Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# When making commits that are strictly formatting/style changes, add the
# commit hash here, so git blame can ignore the change.
#
# For more details, see:
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-blameignoreRevsFile

# run prettier
b9a919ddcd4f5ceb5a6eb24eb6a975504b574e83
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"db:recreate": "node --conditions=react-server -r ts-node/register src/db/scripts/devtools db:recreate",
"db:migrate": "node --conditions=react-server -r ts-node/register src/db/scripts/devtools db:migrate",
"db:seed": "node --conditions=react-server -r ts-node/register src/db/scripts/devtools db:seed",
"lint": "next lint",
"lint": "next lint && prettier src --check",
"lint:fix": "next lint --fix && prettier src --write",
"format": "prettier src --write"
},
Expand Down
23 changes: 11 additions & 12 deletions src/app/(auth)/forgot-password/forgot-password-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import {
import { zUserForgotPassword } from "common/validation/user_validation";
import { applyValidationErrors, ResponseKind } from "common/responses";
import { UnreachableCheck } from "common/errors";
import type { ForgotPasswordError, ForgotPasswordSuccess } from "@root/api/v1/auth/forgot-password/route";
import type {
ForgotPasswordError,
ForgotPasswordSuccess,
} from "@root/api/v1/auth/forgot-password/route";

type ForgotPasswordForm = {
username: string;
Expand All @@ -38,7 +41,6 @@ export function ForgotPasswordPage() {
resolver: zodResolver(zUserForgotPassword),
});


const onSubmit = async (data: ForgotPasswordForm) => {
try {
const url = getAPIPath({ kind: APIPath.ForgotPassword });
Expand All @@ -50,16 +52,16 @@ export function ForgotPasswordPage() {
if (e instanceof AxiosError && e.response) {
const response: AxiosResponse<ForgotPasswordError> = e.response;
const data = response.data;
switch(data.kind) {
switch (data.kind) {
case ResponseKind.ValidationError:
applyValidationErrors(setError, data.errors);
break;
default:
UnreachableCheck(data.kind);
toast.error('An unexpected error occurred');
toast.error("An unexpected error occurred");
}
} else {
toast.error('An network error occurred. Please try again.');
toast.error("An network error occurred. Please try again.");
throw e;
}
}
Expand All @@ -72,20 +74,17 @@ export function ForgotPasswordPage() {
<AuthDetails>
<AuthLabel>Username:</AuthLabel>
<AuthGroup>
<AuthInput {...register('username')}/>
<AuthError error={errors.username}/>
<AuthInput {...register("username")} />
<AuthError error={errors.username} />
</AuthGroup>
</AuthDetails>
<AuthButton onClick={handleSubmit(onSubmit)} disabled={isSubmitting}>
Reset Password
</AuthButton>
</AuthForm>
<AuthLinks>
<AuthLink href={getPath({ kind: Path.AccountLogin })}>
Go back to login
</AuthLink>
<AuthLink href={getPath({ kind: Path.AccountLogin })}>Go back to login</AuthLink>
</AuthLinks>
</AuthMain>
);
};

}
2 changes: 1 addition & 1 deletion src/app/(auth)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ async function Page() {
<ForgotPasswordPage />
</DefaultLayout>
);
};
}

export default Page;
10 changes: 3 additions & 7 deletions src/app/(auth)/login/login_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { zUserLogin } from "common/validation/user_validation";
import { applyValidationErrors, ResponseKind } from "common/responses";
import type { UserLoginError, UserLoginSuccess } from "@root/api/v1/auth/login/route";


type LoginForm = {
username: string;
password: string;
Expand All @@ -45,11 +44,10 @@ export function LoginPage() {
resolver: zodResolver(zUserLogin),
});


const onSubmit = async (data: LoginForm) => {
try {
const url = getAPIPath({ kind: APIPath.Login });
const response: AxiosResponse<UserLoginSuccess> = await http.post(url, {
const response: AxiosResponse<UserLoginSuccess> = await http.post(url, {
username: data.username,
password: data.password,
});
Expand Down Expand Up @@ -100,12 +98,10 @@ export function LoginPage() {
<AuthLink href={getPath({ kind: Path.AccountForgotPassword })}>
Forgot your password
</AuthLink>
<AuthLink href={getPath({ kind: Path.AccountRegister })}>
Register an account
</AuthLink>
<AuthLink href={getPath({ kind: Path.AccountRegister })}>Register an account</AuthLink>
</AuthLinks>
</AuthMain>
);
};
}

export default LoginPage;
2 changes: 1 addition & 1 deletion src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const metadata: Metadata = {
title: "Login",
};

const Page: FunctionComponent = async() => {
const Page: FunctionComponent = async () => {
const session = await getSession();
if (session != null) {
redirect("/");
Expand Down
1 change: 0 additions & 1 deletion src/app/(auth)/logout/logout_redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import http from "client/http";
import { getPath, Path } from "client/paths";
import { useSessionWithUpdate } from "client/sessions";


export const LogoutRedirect: FunctionComponent = () => {
const { setSession } = useSessionWithUpdate();
const router = useRouter();
Expand Down
1 change: 0 additions & 1 deletion src/app/(auth)/logout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import http from "client/http";
import { getPath, Path } from "client/paths";
import { useSessionWithUpdate } from "client/sessions";


const Page: FunctionComponent = () => {
const router = useRouter();
const { setSession } = useSessionWithUpdate();
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/password-reset/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ async function Page() {
<PasswordResetPage />
</DefaultLayout>
);
};
}

export default Page;
27 changes: 13 additions & 14 deletions src/app/(auth)/password-reset/password-reset-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ type PasswordResetForm = {
};

export function PasswordResetPage() {
const [token, setToken] = useState('');
const [token, setToken] = useState("");
const [tokenBad, setTokenBad] = useState(true);
const router = useRouter();

// Get the token from the URL and check if it's "valid" (not empty)
useEffect(() => {
const t = new URLSearchParams(window.location.search).get('token') ?? '';
const t = new URLSearchParams(window.location.search).get("token") ?? "";
const bad = t.length == 0;
if (bad) {
toast.error('Invalid token');
toast.error("Invalid token");
} else {
setToken(t);
setTokenBad(bad);
Expand All @@ -63,26 +63,26 @@ export function PasswordResetPage() {
token: token,
password: data.password,
});
toast.success('Successfully reset password!');
toast.success("Successfully reset password!");
router.push(getPath({ kind: Path.AccountLogin }));
} catch (e) {
if (e instanceof AxiosError && e.response) {
const response: AxiosResponse<ResetPasswordError> = e.response;
const data = response.data;
switch(data.kind) {
switch (data.kind) {
case ResponseKind.ValidationError:
applyValidationErrors(setError, data.errors);
if (data.errors.token != null && data.errors.token.length > 0) {
toast.error('The password reset token is invalid');
toast.error("The password reset token is invalid");
setTokenBad(true);
}
break;
default:
UnreachableCheck(data.kind);
toast.error('An unexpected error occurred');
toast.error("An unexpected error occurred");
}
} else {
toast.error('An network error occurred. Please try again.');
toast.error("An network error occurred. Please try again.");
throw e;
}
}
Expand All @@ -95,13 +95,13 @@ export function PasswordResetPage() {
<AuthDetails>
<AuthLabel>Password:</AuthLabel>
<AuthGroup>
<AuthInput type="password" disabled={tokenBad} {...register('password')}/>
<AuthError error={errors.password}/>
<AuthInput type="password" disabled={tokenBad} {...register("password")} />
<AuthError error={errors.password} />
</AuthGroup>
<AuthLabel>Confirm Password:</AuthLabel>
<AuthGroup>
<AuthInput type="password" disabled={tokenBad} {...register('confirmPassword')}/>
<AuthError error={errors.confirmPassword}/>
<AuthInput type="password" disabled={tokenBad} {...register("confirmPassword")} />
<AuthError error={errors.confirmPassword} />
</AuthGroup>
</AuthDetails>
<AuthButton onClick={handleSubmit(onSubmit)} disabled={isSubmitting || tokenBad}>
Expand All @@ -110,5 +110,4 @@ export function PasswordResetPage() {
</AuthForm>
</AuthMain>
);
};

}
2 changes: 1 addition & 1 deletion src/app/(auth)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ export default async function Page() {
<RegisterPage />
</DefaultLayout>
);
};
}
7 changes: 2 additions & 5 deletions src/app/(auth)/register/register_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { zUserRegister } from "common/validation/user_validation";
import { applyValidationErrors, ResponseKind } from "common/responses";
import { UserRegisterError, UserRegisterSuccess } from "@root/api/v1/auth/register/route";


type RegisterForm = {
email: string;
username: string;
Expand All @@ -50,7 +49,7 @@ export function RegisterPage() {
const onSubmit = async (data: RegisterForm) => {
try {
const url = getAPIPath({ kind: APIPath.Register });
const response: AxiosResponse<UserRegisterSuccess> = await http.post(url, {
const response: AxiosResponse<UserRegisterSuccess> = await http.post(url, {
email: data.email,
username: data.username,
password: data.password,
Expand Down Expand Up @@ -110,9 +109,7 @@ export function RegisterPage() {
</AuthButton>
</AuthForm>
<AuthLinks>
<AuthLink href={getPath({ kind: Path.AccountLogin })}>
Already have an account
</AuthLink>
<AuthLink href={getPath({ kind: Path.AccountLogin })}>Already have an account</AuthLink>
</AuthLinks>
</AuthMain>
);
Expand Down
32 changes: 22 additions & 10 deletions src/app/admin/contests/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { Metadata } from "next";
import Link from "next/link";
import { ReactNode } from "react";
import { db } from "db";
import { AdminTable, AdminTbody, AdminTD, AdminTH, AdminThead, AdminTR } from "client/components/admin_table/admin_table";
import {
AdminTable,
AdminTbody,
AdminTD,
AdminTH,
AdminThead,
AdminTR,
} from "client/components/admin_table/admin_table";
import { DefaultLayout } from "client/components/layouts/default_layout";
import { ContestCreator } from "client/components/contest_creator";
import { EmptyNotice } from "client/components/empty_notice";
Expand All @@ -27,7 +34,7 @@ type ContestSummaryAdminDTO = {

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- pre-existing error before eslint inclusion
async function getContestsData(session: SessionData): Promise<ContestSummaryAdminDTO[]> {
const contests = await db
const contests = (await db
.selectFrom("contests")
.innerJoin("users", "users.id", "contests.owner_id")
.select([
Expand All @@ -39,24 +46,23 @@ async function getContestsData(session: SessionData): Promise<ContestSummaryAdmi
])
.orderBy("contests.created_at", "desc")
.limit(1000)
.execute() satisfies ContestSummaryAdminDTO[];
.execute()) satisfies ContestSummaryAdminDTO[];

return contests;
}


async function Page() {
const session = await getSession();

if (session == null || !canManageContests(session)) {
return <ForbiddenPage/>;
return <ForbiddenPage />;
}

const contests = await getContestsData(session);

let content: ReactNode = null;
if (contests.length == 0) {
content = <EmptyNotice className="mt-12"/>;
content = <EmptyNotice className="mt-12" />;
} else {
content = (
<AdminTable className="mt-6">
Expand All @@ -75,15 +81,21 @@ async function Page() {
<AdminTR key={contest.slug}>
<AdminTD className="font-mono text-sm">{uuidToHuradoID(contest.id)}</AdminTD>
<AdminTD className="font-mono text-sm">
<Link href={getPath({ kind: Path.ContestView, slug: contest.slug })} className="text-blue-400 hover:text-blue-500">
<Link
href={getPath({ kind: Path.ContestView, slug: contest.slug })}
className="text-blue-400 hover:text-blue-500"
>
{contest.slug}
</Link>
</AdminTD>
<AdminTD>{contest.title}</AdminTD>
<AdminTD>{contest.owner}</AdminTD>
<AdminTD>{contest.created_at.toLocaleDateString()}</AdminTD>
<AdminTD>
<Link href={getPath({ kind: Path.ContestEdit, uuid: contest.id })} className="text-blue-400 hover:text-blue-500">
<Link
href={getPath({ kind: Path.ContestEdit, uuid: contest.id })}
className="text-blue-400 hover:text-blue-500"
>
Edit
</Link>
</AdminTD>
Expand All @@ -98,11 +110,11 @@ async function Page() {
<DefaultLayout>
<div className="flex justify-between items-center">
<h2 className="text-3xl">Contests</h2>
<ContestCreator/>
<ContestCreator />
</div>
{content}
</DefaultLayout>
);
};
}

export default Page;
7 changes: 2 additions & 5 deletions src/app/admin/files/[hash]/[filename]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ export async function GET(request: NextRequest, context: NextContext<RouteParams
// There's no way to limit access to files. All we keep track of is their hash and size.
// If someone knows the hash of a file, they might as well know the file itself.
const session = await getSession(request);
const isAdmin = (
canManageTasks(session) ||
canManageContests(session) ||
canManageContests(session)
);
const isAdmin =
canManageTasks(session) || canManageContests(session) || canManageContests(session);

if (!isAdmin) {
return NextResponse.json({}, { status: 403 });
Expand Down
4 changes: 2 additions & 2 deletions src/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export const metadata: Metadata = {
title: "Admin",
};

const Page: FunctionComponent = async() => {
const Page: FunctionComponent = async () => {
const session = await getSession();

const tasks = canManageTasks(session);
const sets = canManageProblemSets(session);
const contests = canManageContests(session);
if (!tasks && !sets && !contests) {
return <ForbiddenPage/>;
return <ForbiddenPage />;
}

return (
Expand Down
Loading
Loading