Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement user login #861

Draft
wants to merge 43 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9322570
Basic login
ShouvikGhosh2048 Sep 1, 2024
0cf48a5
Add user claims
ShouvikGhosh2048 Sep 2, 2024
c040edd
Implement verification
ShouvikGhosh2048 Sep 3, 2024
3eea913
Shift login to top right
ShouvikGhosh2048 Sep 3, 2024
1430714
tweak page URLs
otobot1 Sep 14, 2024
d691dcb
tweak wording on index page
otobot1 Sep 14, 2024
1cb0d26
Update schema.prisma
otobot1 Sep 15, 2024
e40cf99
refactor checkIsPrivileged to support arrays of targetUserIds
otobot1 Sep 15, 2024
009e6d7
remove unused variable
otobot1 Sep 15, 2024
87415b0
fix up tech and techVideo routers
otobot1 Sep 15, 2024
6959303
move userClaim pages into pages directory
otobot1 Sep 15, 2024
2f81244
refactor userClaim api
otobot1 Sep 15, 2024
34002f5
Update userClaim.ts
otobot1 Sep 15, 2024
0aed77a
create new baseline migration
otobot1 Sep 15, 2024
3fb089d
show "claim user" message to all users
otobot1 Sep 27, 2024
75eb0f7
Update /claim-user to work with API changes
otobot1 Sep 27, 2024
c1b21e9
increase query invalidation specificity
otobot1 Sep 28, 2024
a187771
prefer import syntax: `type {...}` over `{type ...}`
otobot1 Sep 28, 2024
119c1bc
refactoring, formatting, and referencing constants
otobot1 Sep 28, 2024
39e1cff
Update schema.prisma
otobot1 Sep 28, 2024
2fea675
resolve webpack errors
otobot1 Sep 28, 2024
da682d9
work on getting userClaim pages to load
otobot1 Sep 28, 2024
881b720
get /claim-user working and tweak styling and wording
otobot1 Oct 20, 2024
05781a5
get /claim-user/verify working
otobot1 Oct 20, 2024
f97d6e4
rename /claim-user to /claim-users
otobot1 Oct 20, 2024
5ef6367
fix double call of layout component
otobot1 Oct 20, 2024
8e2e119
update comment
otobot1 Oct 20, 2024
f5d09c2
update /claim-user pathname constants
otobot1 Oct 20, 2024
2b36ea7
add link to verify page from claim page
otobot1 Oct 20, 2024
70e52aa
address type error
otobot1 Oct 20, 2024
f27308a
tidy up types and adjust comments
otobot1 Oct 20, 2024
c54eb60
Update schema.prisma
otobot1 Oct 20, 2024
cecf355
resolve remaining TODOs
otobot1 Oct 20, 2024
fe926a9
remove remaining TODO!!! comments
otobot1 Oct 25, 2024
0db568f
remove old settings button and rearrange remaining footer links
otobot1 Jan 5, 2025
77021ea
cleanup header JSX
otobot1 Jan 5, 2025
c317643
add `as const`
otobot1 Jan 5, 2025
31ef5ef
finish cleaning up header JSX and fixing CSS
otobot1 Jan 5, 2025
ba70659
add more whitespace
otobot1 Jan 5, 2025
b7b63e0
add new settings button
otobot1 Jan 5, 2025
dad1ec0
Update schema.prisma
otobot1 Jan 6, 2025
447295c
revert dad1ec0d9a56c5ba57528811a8c09fe66cec7358
otobot1 Jan 6, 2025
98453c7
start troubleshooting verify user transaction
otobot1 Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions prisma/migrations/20240902061032_add_user_claims/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
otobot1 marked this conversation as resolved.
Show resolved Hide resolved
Warnings:

- A unique constraint covering the columns `[sessionToken]` on the table `session` will be added. If there are existing duplicate values, this will fail.

*/
-- CreateTable
CREATE TABLE `user-claim` (
`claimByUserId` VARCHAR(191) NOT NULL,
`claimForUserId` VARCHAR(191) NOT NULL,
`id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,

INDEX `user-claim_claimByUserId_idx`(`claimByUserId`),
UNIQUE INDEX `user-claim_claimByUserId_claimForUserId_key`(`claimByUserId`, `claimForUserId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateIndex
CREATE UNIQUE INDEX `session_sessionToken_key` ON `session`(`sessionToken`);

-- AddForeignKey
ALTER TABLE `user-claim` ADD CONSTRAINT `user-claim_claimByUserId_fkey` FOREIGN KEY (`claimByUserId`) REFERENCES `user`(`id`) ON DELETE CASCADE ON UPDATE RESTRICT;

-- AddForeignKey
ALTER TABLE `user-claim` ADD CONSTRAINT `user-claim_claimForUserId_fkey` FOREIGN KEY (`claimForUserId`) REFERENCES `user`(`id`) ON DELETE CASCADE ON UPDATE RESTRICT;
14 changes: 14 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,18 @@ model UsersToCompletedMaps {
@@map("users-to-completed-maps")
}

model UserClaim {
User_UserClaim_claimBy User @relation("UserClaim_claimBy", fields: [claimByUserId], references: [id], onDelete: Cascade, onUpdate: Restrict)
claimByUserId String
User_UserClaim_claimFor User @relation("UserClaim_claimFor", fields: [claimForUserId], references: [id], onDelete: Cascade, onUpdate: Restrict)
claimForUserId String
id Int @id @default(autoincrement()) @db.UnsignedMediumInt

@@unique([claimByUserId, claimForUserId])
@@index([claimByUserId])
@@map("user-claim")
}

// Next auth models
model User {
id String @id @default(cuid())
Expand Down Expand Up @@ -616,6 +628,8 @@ model User {
Map_NewWithMod_New_submittedByToUser Map_NewWithMod_New[] @relation("Map_NewWithMod_New_SubmittedByToUser")
Map_NewSolo_mapperUserIdToUser Map_NewSolo[] @relation("Map_NewSolo_MapperUserIdToUser")
Map_NewSolo_submittedByToUser Map_NewSolo[] @relation("Map_NewSolo_SubmittedByToUser")
UserClaim_UserClaim_claimBy UserClaim[] @relation("UserClaim_claimBy")
UserClaim_UserClaim_claimFor UserClaim[] @relation("UserClaim_claimFor")

@@unique([discordUsername, discordDiscriminator])
@@map("user")
Expand Down
2 changes: 0 additions & 2 deletions src/components/layout/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ const useStyles = createStyles(
export const Footer = () => {
const { classes } = useStyles();


return (
<Box className={classes.outerFooter}>
<hr className={classes.horizontalRule} />
Expand All @@ -50,7 +49,6 @@ export const Footer = () => {
align="start"
spacing="1px"
>
<Link href={COMING_SOON_PATHNAME}>My Account</Link>
<Link href={COMING_SOON_PATHNAME}>Settings</Link>
</Stack>
<Link
Expand Down
10 changes: 8 additions & 2 deletions src/components/layout/header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flex, createStyles, Title, Box } from "@mantine/core";
import { Flex, createStyles, Title, Button } from "@mantine/core";
import { signIn, signOut, useSession } from "next-auth/react";
import Image from "next/image";
import cmlLogo from "~/../public/images/logo/cml_logo.png";
import { blackBackgroundColor } from "~/styles/layoutColors";
Expand Down Expand Up @@ -31,6 +32,7 @@ export const Header = () => {
const height = 115;
const width = height / 694 * 774;

const { data: session } = useSession();

return (
<header>
Expand All @@ -43,7 +45,11 @@ export const Header = () => {
alt="CML Logo"
/>
<Title className={classes.siteTitle} order={1}>Celeste Mods List</Title>
<Box w={width} />
<Flex w={width} gap="sm" align="center" justify="flex-end">
{ !session && <Button onClick={() => { void signIn("discord"); }}>Login</Button> }
{ session && <span>{session.user.name}</span> }
{ session && <Button onClick={() => { void signOut(); }}>Logout</Button> }
</Flex>
</Flex>
</header>
);
Expand Down
4 changes: 3 additions & 1 deletion src/consts/pathnames.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const MODS_PAGE_PATHNAME = "/mods";
export const FAQ_PAGE_PATHNAME = "/faq";
export const COMING_SOON_PATHNAME = "/coming-soon";
export const COMING_SOON_PATHNAME = "/coming-soon";
export const CLAIM_USER_PATHNAME = "/claim-user";
export const VERIFY_CLAIM_PATHNAME = "/verify-claim";
115 changes: 115 additions & 0 deletions src/pages/claim-user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Button, createStyles, ScrollArea, Table } from "@mantine/core";
import { type NextPage } from "next";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { Layout } from "~/components/layout/layout";
import { cmlDiscordInviteUrl } from "~/consts/cmlDiscordInviteUrl";
import { CLAIM_USER_PATHNAME } from "~/consts/pathnames";
import { pageContentHeightPixels } from "~/styles/pageContentHeightPixels";
import { api } from "~/utils/api";

const useStyles = createStyles(
(theme) => ({
scrollArea: {
height: `${pageContentHeightPixels}px`,
color: theme.white,
},
discordLink: {
textDecoration: 'underline',
}
}),
);

const ClaimUser : NextPage = () => {
const { status } = useSession();

const utils = api.useUtils();
const unlinkedUsersQuery = api.user.getUnlinked.useQuery({}, { queryKey: ["user.getUnlinked", {}] });
const unlinkedUsers = unlinkedUsersQuery.data ?? [];
const claimsQuery = api.user.getUserClaims.useQuery(undefined, { queryKey: ["user.getUserClaims", undefined] });
const claims = claimsQuery.data ?? [];
const unclaimedUsers = unlinkedUsers.filter(user => !claims.find(claim => claim.claimForUserId === user.id));

const createUserClaimMutation = api.user.createUserClaim.useMutation({
onSuccess() {
void utils.user.getUserClaims.invalidate();
}
});

const { classes } = useStyles();

if (status === 'unauthenticated') {
return (
<Layout
pageTitle="Claim user"
pageDescription="Claim user"
pathname={CLAIM_USER_PATHNAME}
>
Login to claim users.
</Layout>
);
}

return (
<Layout
pageTitle="Claim user"
pageDescription="Claim user"
pathname={CLAIM_USER_PATHNAME}
>
<ScrollArea
offsetScrollbars
className={classes.scrollArea}>
<h1>Claim user</h1>
<h2>Claimed users</h2>
<p>
Contact us on <Link href={cmlDiscordInviteUrl} className={classes.discordLink} target="_blank">Discord</Link> to get your claim verified.
</p>
<Table>
<thead>
<tr>
<th>Claim ID</th>
<th>User ID</th>
<th>Username</th>
</tr>
</thead>
<tbody>
{claims.map(claim => (
<tr key={claim.id}>
<td>{claim.id}</td>
<td>{claim.claimForUserId}</td>
<td>{claim.User_UserClaim_claimFor.discordUsername}#{claim.User_UserClaim_claimFor.discordDiscriminator}</td>
</tr>
))}
</tbody>
</Table>
<h2>Users available</h2>
<Table>
<thead>
<tr>
<th>User</th>
<th></th>
</tr>
</thead>
<tbody>
{unclaimedUsers.map(user => (
<tr key={user.id}>
<td>{user.discordUsername}#{user.discordDiscriminator}</td>
<td>
<Button onClick={() => {
if (!createUserClaimMutation.isLoading) {
createUserClaimMutation.mutate({
forUserId: user.id
});
}
}} disabled={createUserClaimMutation.isLoading}>Claim</Button>
</td>
</tr>
))}
</tbody>
</Table>
</ScrollArea>
</Layout>
);
}

export default ClaimUser;
8 changes: 8 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createStyles, Flex, ScrollArea } from "@mantine/core";
import { type NextPage } from "next";
import { useSession } from "next-auth/react";
import Image from "next/image";
import Link from "next/link";
import { Layout } from "~/components/layout/layout";
Expand Down Expand Up @@ -53,6 +54,7 @@ const Home: NextPage = () => {
const { classes } = useStyles();
const height = 280;
const width = height / 577 * 867;
const { status } = useSession();

return (
<Layout
Expand All @@ -77,6 +79,12 @@ const Home: NextPage = () => {
<h2>CML Public Beta</h2>
<p>Welcome! The site is currently in early beta.</p>
<p>For now, <Link className={classes.link} href={MODS_PAGE_PATHNAME}>mods</Link> can only be browsed.</p>
{ status === "authenticated" && (
<p>
If you submitted ratings via the google form, you can claim your old user
from <Link className={classes.link} href="/claim-user">here</Link>.
</p>
) }
<h2>Community Projects</h2>
<h3 style={{ marginTop: "2px" }}>Celeste Mountain Lego Idea</h3>
<Image
Expand Down
120 changes: 120 additions & 0 deletions src/pages/verify-claim.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Button, createStyles, Modal, ScrollArea, Stack, Table } from "@mantine/core";
import { type NextPage } from "next";
import { useSession } from "next-auth/react";
import { useState } from "react";
import { Layout } from "~/components/layout/layout";
import { VERIFY_CLAIM_PATHNAME } from "~/consts/pathnames";
import { pageContentHeightPixels } from "~/styles/pageContentHeightPixels";
import { api } from "~/utils/api";

const useStyles = createStyles(
(theme) => ({
scrollArea: {
height: `${pageContentHeightPixels}px`,
color: theme.white,
},
}),
);

const VerifyClaim : NextPage = () => {
const { data: session, status } = useSession();

const utils = api.useUtils();
const userClaimsQuery = api.user.getAllUserClaims.useQuery();
const userClaims = userClaimsQuery.data ?? [];

const verifyUserClaimMutation = api.user.verifyUserClaim.useMutation({
async onSuccess() {
await Promise.all([
utils.user.getAllUserClaims.invalidate(),
utils.user.getUserClaims.invalidate()
]);
setClaimToVerify(null);
}
});

const [claimToVerify, setClaimToVerify] = useState<{ id: number, byUser: string, forUser: string } | null>(null);

const { classes } = useStyles();

if (status === 'loading') {
return (
<Layout
pageTitle="Verify claim"
pageDescription="Verify claim"
pathname={VERIFY_CLAIM_PATHNAME}
>
<></>
</Layout>
);
}
else if (session === null || !(session.user.permissions.find(p => p === 'Admin' || p === 'Super_Admin'))) {
return (
<Layout
pageTitle="Verify claim"
pageDescription="Verify claim"
pathname={VERIFY_CLAIM_PATHNAME}
>
Login as an admin/superadmin to verify users.
</Layout>
);
}

return (
<Layout
pageTitle="Verify claim"
pageDescription="Verify claim"
pathname={VERIFY_CLAIM_PATHNAME}
>
<Modal opened={claimToVerify !== null} onClose={() => { setClaimToVerify(null); }} title="Confirmation" centered>
{ claimToVerify && (
<Stack align="flex-end">
<p>
Are you sure you want to verify claim {claimToVerify.id} by {claimToVerify.byUser} for {claimToVerify.forUser}?
</p>
<Button onClick={() => {
verifyUserClaimMutation.mutate({ id: claimToVerify.id });
}}>Verify</Button>
</Stack>
)}
</Modal>
<ScrollArea
offsetScrollbars
className={classes.scrollArea}>
<h1>User claims</h1>
<Table>
<thead>
<tr>
<th>Claim ID</th>
<th>By</th>
<th>For</th>
<th></th>
</tr>
</thead>
<tbody>
{userClaims.map(claim => {
const byUser = `${claim.User_UserClaim_claimBy.discordUsername}#${claim.User_UserClaim_claimBy.discordDiscriminator}`;
const forUser = `${claim.User_UserClaim_claimFor.discordUsername}#${claim.User_UserClaim_claimFor.discordDiscriminator}`;
return (
<tr key={claim.id}>
<td>{claim.id}</td>
<td>{byUser}</td>
<td>{forUser}</td>
<td><Button onClick={() => {
setClaimToVerify({
id: claim.id,
byUser,
forUser,
});
}}>Verify</Button></td>
</tr>
)
})}
</tbody>
</Table>
</ScrollArea>
</Layout>
);
}

export default VerifyClaim;
Loading