-
- 🔒 Private Game - Invite Code:
-
+
Invite Code
-
+
diff --git a/libs/frontend/src/routes/(authenticated)/multiplayer/[id]/+page.svelte b/libs/frontend/src/routes/(authenticated)/multiplayer/[id]/+page.svelte
index a29c9a22..e9ca48d8 100644
--- a/libs/frontend/src/routes/(authenticated)/multiplayer/[id]/+page.svelte
+++ b/libs/frontend/src/routes/(authenticated)/multiplayer/[id]/+page.svelte
@@ -12,7 +12,8 @@
import Loader from "@/components/ui/loader/loader.svelte";
import LogicalUnit from "@/components/ui/logical-unit/logical-unit.svelte";
import * as Resizable from "@/components/ui/resizable";
- import { apiUrls } from "@/config/api";
+ import { buildBackendUrl } from "@/config/backend";
+ import { backendUrls } from "types";
import { buildWebSocketUrl } from "@/config/websocket";
import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie";
import Chat from "@/features/chat/components/chat.svelte";
@@ -49,7 +50,7 @@
type GameRequest,
type GameResponse
} from "types";
- import { testIds } from "@/config/test-ids";
+ import { testIds } from "types";
function isUserIdInUserList(
userId: string,
@@ -196,10 +197,13 @@
userId: $authenticatedUserInfo.userId
};
- await fetchWithAuthenticationCookie(apiUrls.SUBMIT_GAME, {
- body: JSON.stringify(gameSubmissionParams),
- method: httpRequestMethod.POST
- });
+ await fetchWithAuthenticationCookie(
+ buildBackendUrl(backendUrls.SUBMISSION_GAME),
+ {
+ body: JSON.stringify(gameSubmissionParams),
+ method: httpRequestMethod.POST
+ }
+ );
sendGameMessage({
event: gameEventEnum.SUBMITTED_PLAYER
diff --git a/libs/frontend/src/routes/(authenticated)/puzzles/[id]/edit/+page.server.ts b/libs/frontend/src/routes/(authenticated)/puzzles/[id]/edit/+page.server.ts
index 1c351b79..28bb23de 100644
--- a/libs/frontend/src/routes/(authenticated)/puzzles/[id]/edit/+page.server.ts
+++ b/libs/frontend/src/routes/(authenticated)/puzzles/[id]/edit/+page.server.ts
@@ -4,17 +4,19 @@ import { buildBackendUrl } from "@/config/backend";
import {
backendUrls,
cookieKeys,
+ DELETE,
deletePuzzleSchema,
editPuzzleSchema,
+ ERROR_MESSAGES,
environment,
+ frontendUrls,
httpResponseCodes,
PUT,
type EditPuzzle
} from "types";
-import { error, fail } from "@sveltejs/kit";
+import { error, fail, redirect } from "@sveltejs/kit";
import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie.js";
import type { PageServerLoadEvent, RequestEvent } from "./$types.js";
-import { handleDeletePuzzleForm } from "../../../../api/handle-delete-puzzle-form.js";
export async function load({ fetch, params, cookies }: PageServerLoadEvent) {
const id = params.id;
@@ -47,7 +49,38 @@ export async function load({ fetch, params, cookies }: PageServerLoadEvent) {
}
export const actions = {
- deletePuzzle: handleDeletePuzzleForm,
+ deletePuzzle: async ({ params, request }: RequestEvent) => {
+ const deletePuzzleForm = await superValidate(
+ request,
+ zod4(deletePuzzleSchema)
+ );
+
+ if (!deletePuzzleForm.valid) {
+ return fail(httpResponseCodes.CLIENT_ERROR.BAD_REQUEST, {
+ deletePuzzleForm
+ });
+ }
+
+ const id = deletePuzzleForm.data.id;
+ const deletePuzzleUrl = buildBackendUrl(backendUrls.puzzleById(id));
+
+ const response = await fetchWithAuthenticationCookie(deletePuzzleUrl, {
+ headers: {
+ "Content-Type": "application/json",
+ Cookie: request.headers.get("cookie") || ""
+ },
+ method: DELETE
+ });
+
+ if (!response.ok) {
+ return fail(response.status, {
+ deletePuzzleForm,
+ error: ERROR_MESSAGES.PUZZLE.FAILED_TO_DELETE
+ });
+ }
+
+ redirect(httpResponseCodes.REDIRECTION.SEE_OTHER, frontendUrls.PUZZLES);
+ },
editPuzzle: async ({ params, request }: RequestEvent) => {
const form = await superValidate(request, zod4(editPuzzleSchema));
@@ -72,7 +105,7 @@ export const actions = {
if (!response.ok) {
return fail(response.status, {
- error: "Failed to update the puzzle.",
+ error: ERROR_MESSAGES.PUZZLE.FAILED_TO_UPDATE,
form
});
}
diff --git a/libs/frontend/src/routes/(authenticated)/puzzles/create/+page.server.ts b/libs/frontend/src/routes/(authenticated)/puzzles/create/+page.server.ts
index c2791af1..fddd5537 100644
--- a/libs/frontend/src/routes/(authenticated)/puzzles/create/+page.server.ts
+++ b/libs/frontend/src/routes/(authenticated)/puzzles/create/+page.server.ts
@@ -1,6 +1,13 @@
import { superValidate } from "sveltekit-superforms";
import { zod4 } from "sveltekit-superforms/adapters";
-import { backendUrls, createPuzzleSchema, frontendUrls, POST } from "types";
+import {
+ backendUrls,
+ createPuzzleSchema,
+ ERROR_MESSAGES,
+ frontendUrls,
+ httpResponseCodes,
+ POST
+} from "types";
import { buildBackendUrl } from "@/config/backend";
import { fail, redirect } from "@sveltejs/kit";
import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie";
@@ -17,7 +24,10 @@ export const actions = {
const form = await superValidate(request, zod4(createPuzzleSchema));
if (!form.valid) {
- fail(400, { form });
+ return fail(httpResponseCodes.CLIENT_ERROR.BAD_REQUEST, {
+ form,
+ message: ERROR_MESSAGES.FORM.VALIDATION_ERRORS
+ });
}
const cookie = request.headers.get("cookie") || "";
@@ -37,11 +47,14 @@ export const actions = {
const data = await result.json();
if (!result.ok) {
- fail(400, { form, message: data.message });
+ return fail(httpResponseCodes.CLIENT_ERROR.BAD_REQUEST, {
+ form,
+ message: data.message || ERROR_MESSAGES.PUZZLE.FAILED_TO_CREATE
+ });
}
const editPuzzleUrl = frontendUrls.puzzleByIdEdit(data._id);
- throw redirect(302, editPuzzleUrl);
+ throw redirect(httpResponseCodes.REDIRECTION.FOUND, editPuzzleUrl);
}
};
diff --git a/libs/frontend/src/routes/(unauthenticated-only)/login/+page.server.ts b/libs/frontend/src/routes/(unauthenticated-only)/login/+page.server.ts
index 120fd149..39fb7295 100644
--- a/libs/frontend/src/routes/(unauthenticated-only)/login/+page.server.ts
+++ b/libs/frontend/src/routes/(unauthenticated-only)/login/+page.server.ts
@@ -2,10 +2,18 @@ import { superValidate } from "sveltekit-superforms";
import { zod4 } from "sveltekit-superforms/adapters";
import type { RequestEvent } from "./$types";
import { fail, redirect } from "@sveltejs/kit";
-import { frontendUrls, httpResponseCodes, loginSchema } from "types";
+import {
+ backendUrls,
+ ERROR_MESSAGES,
+ frontendUrls,
+ httpRequestMethod,
+ httpResponseCodes,
+ loginSchema
+} from "types";
import { setCookie } from "@/features/authentication/utils/set-cookie";
-import { login } from "../../api/login";
import { searchParamKeys } from "@/config/search-params";
+import { buildBackendUrl } from "@/config/backend";
+import type { LoginRequest } from "types/dist/core/api/schema/auth/login.schema";
export async function load() {
const form = await superValidate(zod4(loginSchema));
@@ -20,11 +28,20 @@ export const actions = {
if (!form.valid) {
return fail(httpResponseCodes.CLIENT_ERROR.BAD_REQUEST, {
form,
- message: "Form errors"
+ message: ERROR_MESSAGES.FORM.VALIDATION_ERRORS
});
}
- const result = await login(form.data.identifier, form.data.password);
+ const payload: LoginRequest = {
+ identifier: form.data.identifier,
+ password: form.data.password
+ };
+
+ const result = await fetch(buildBackendUrl(backendUrls.LOGIN), {
+ method: httpRequestMethod.POST,
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload)
+ });
const data = await result.json();
if (!result.ok) {
diff --git a/libs/frontend/src/routes/(unauthenticated-only)/register/+page.server.ts b/libs/frontend/src/routes/(unauthenticated-only)/register/+page.server.ts
index 01c4bb98..3b182fca 100644
--- a/libs/frontend/src/routes/(unauthenticated-only)/register/+page.server.ts
+++ b/libs/frontend/src/routes/(unauthenticated-only)/register/+page.server.ts
@@ -4,6 +4,7 @@ import { fail, redirect } from "@sveltejs/kit";
import { buildBackendUrl } from "@/config/backend";
import {
backendUrls,
+ ERROR_MESSAGES,
frontendUrls,
httpResponseCodes,
POST,
@@ -25,7 +26,7 @@ export const actions = {
if (!form.valid) {
return fail(httpResponseCodes.CLIENT_ERROR.BAD_REQUEST, {
form,
- message: "Form errors"
+ message: ERROR_MESSAGES.FORM.VALIDATION_ERRORS
});
}
diff --git a/libs/frontend/src/routes/+error.svelte b/libs/frontend/src/routes/+error.svelte
index 2b19766e..908c779f 100644
--- a/libs/frontend/src/routes/+error.svelte
+++ b/libs/frontend/src/routes/+error.svelte
@@ -1,21 +1,108 @@
-
-
- {page.status} - {page.error?.message || "Something broke."}
+
+ Error - CodinCod
+
+
+
+
+
+
+
+
+
+
+ {page.status === 404 ? "Page Not Found" : "Something Went Wrong"}
+
+
+
+ {#if page.status === 404}
+ The page you're looking for doesn't exist or has been moved.
+ {:else if page.status >= 500}
+ We're experiencing technical difficulties. Please try again later.
+ {:else}
+ {page.error?.message || "An unexpected error occurred."}
+ {/if}
+
+
+ {#if page.status !== 404}
+
+ Error code: {page.status}
+
+ {/if}
+
+
+
+
+
+
+ {#if page.status !== 404}
+
+ {/if}
+
-
Go back to the .
+ {#if import.meta.env.DEV && page.error}
+
+
+ Debug Information (Dev Only)
+
+ {JSON.stringify(
+ {
+ message: page.error.message,
+ status: page.status,
+ path: page.url.pathname,
+ stack: (page.error as Error & { stack?: string }).stack
+ },
+ null,
+ 2
+ )}
+
+ {/if}
+
diff --git a/libs/frontend/src/routes/api/account/preferences/+server.ts b/libs/frontend/src/routes/api/account/preferences/+server.ts
deleted file mode 100644
index 41f1d142..00000000
--- a/libs/frontend/src/routes/api/account/preferences/+server.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls } from "types";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ request }: RequestEvent) {
- const body = await request.text();
-
- return fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.ACCOUNT_PREFERENCES),
- {
- body: body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- }
- );
-}
-
-export async function PUT({ request }: RequestEvent) {
- const body = await request.text();
-
- return fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.ACCOUNT_PREFERENCES),
- {
- body: body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.PUT
- }
- );
-}
-
-export async function GET({ request }: RequestEvent) {
- return fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.ACCOUNT_PREFERENCES),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- }
- );
-}
-
-export async function DELETE({ request }: RequestEvent) {
- return fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.ACCOUNT_PREFERENCES),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.DELETE
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/comment/[id]/+server.ts b/libs/frontend/src/routes/api/comment/[id]/+server.ts
deleted file mode 100644
index b404b592..00000000
--- a/libs/frontend/src/routes/api/comment/[id]/+server.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function GET({ params, request }: RequestEvent) {
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.commentById(params.id)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- }
- );
-}
-
-export async function DELETE({ params, request }: RequestEvent) {
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.commentById(params.id)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.DELETE
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/comment/[id]/comment/+server.ts b/libs/frontend/src/routes/api/comment/[id]/comment/+server.ts
deleted file mode 100644
index 24d47f91..00000000
--- a/libs/frontend/src/routes/api/comment/[id]/comment/+server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "../vote/$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ params, request }: RequestEvent) {
- const body = await request.text();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.commentByIdComment(params.id)),
- {
- body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/comment/[id]/vote/+server.ts b/libs/frontend/src/routes/api/comment/[id]/vote/+server.ts
deleted file mode 100644
index 2188e380..00000000
--- a/libs/frontend/src/routes/api/comment/[id]/vote/+server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ params, request }: RequestEvent) {
- const body = await request.text();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.commentByIdVote(params.id)),
- {
- body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/execute-code/+server.ts b/libs/frontend/src/routes/api/execute-code/+server.ts
deleted file mode 100644
index 429aa0be..00000000
--- a/libs/frontend/src/routes/api/execute-code/+server.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ request }: RequestEvent) {
- const body = await request.text();
-
- return fetchWithAuthenticationCookie(buildBackendUrl(backendUrls.EXECUTE), {
- body: body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- });
-}
diff --git a/libs/frontend/src/routes/api/get-user-activity-by-username.ts b/libs/frontend/src/routes/api/get-user-activity-by-username.ts
deleted file mode 100644
index 2bf64306..00000000
--- a/libs/frontend/src/routes/api/get-user-activity-by-username.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls } from "types";
-
-export async function getUserActivityByUsername(username: string) {
- return fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.userByUsernameActivity(username))
- );
-}
diff --git a/libs/frontend/src/routes/api/handle-delete-puzzle-form.ts b/libs/frontend/src/routes/api/handle-delete-puzzle-form.ts
deleted file mode 100644
index 086ac463..00000000
--- a/libs/frontend/src/routes/api/handle-delete-puzzle-form.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { redirect, type RequestEvent } from "@sveltejs/kit";
-import { fail, superValidate } from "sveltekit-superforms";
-import { zod4 } from "sveltekit-superforms/adapters";
-import {
- backendUrls,
- DELETE,
- deletePuzzleSchema,
- frontendUrls,
- httpResponseCodes
-} from "types";
-
-export async function handleDeletePuzzleForm({ request }: RequestEvent) {
- const deletePuzzleForm = await superValidate(
- request,
- zod4(deletePuzzleSchema)
- );
-
- if (!deletePuzzleForm.valid) {
- // Again, return { form } and things will just work.
- fail(400, { deletePuzzleForm });
- }
-
- const cookie = request.headers.get("cookie") || "";
-
- // Prepare the url
- const id = deletePuzzleForm.data.id;
- const deletePuzzleUrl = buildBackendUrl(backendUrls.puzzleById(id));
-
- // Update puzzle data to backend
- const response = await fetchWithAuthenticationCookie(deletePuzzleUrl, {
- headers: {
- "Content-Type": "application/json",
- Cookie: cookie
- },
- method: DELETE
- });
-
- if (!response.ok) {
- fail(response.status, {
- deletePuzzleForm,
- error: "Failed to delete the puzzle."
- });
- }
-
- if (response.ok) {
- redirect(httpResponseCodes.REDIRECTION.SEE_OTHER, frontendUrls.PUZZLES);
- }
-
- // Display a success status message
- return {
- deletePuzzleForm,
- message: "Puzzle deleted successfully!",
- success: true
- };
-}
diff --git a/libs/frontend/src/routes/api/login.ts b/libs/frontend/src/routes/api/login.ts
deleted file mode 100644
index 2257d564..00000000
--- a/libs/frontend/src/routes/api/login.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls, POST } from "types";
-
-export async function login(identifier: string, password: string) {
- return fetchWithAuthenticationCookie(buildBackendUrl(backendUrls.LOGIN), {
- body: JSON.stringify({ identifier, password }),
- method: POST
- });
-}
diff --git a/libs/frontend/src/routes/api/moderation/puzzle/[id]/approve/+server.ts b/libs/frontend/src/routes/api/moderation/puzzle/[id]/approve/+server.ts
deleted file mode 100644
index b195ef69..00000000
--- a/libs/frontend/src/routes/api/moderation/puzzle/[id]/approve/+server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-
-export async function POST({ params, request }: RequestEvent) {
- const { id } = params;
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.moderationPuzzleApprove(id)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST,
- body: JSON.stringify({})
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/moderation/puzzle/[id]/revise/+server.ts b/libs/frontend/src/routes/api/moderation/puzzle/[id]/revise/+server.ts
deleted file mode 100644
index b4688b01..00000000
--- a/libs/frontend/src/routes/api/moderation/puzzle/[id]/revise/+server.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-
-export async function POST({ params, request }: RequestEvent) {
- const { id } = params;
- const body = await request.text();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.moderationPuzzleRevise(id)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST,
- body
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/moderation/report/[id]/resolve/+server.ts b/libs/frontend/src/routes/api/moderation/report/[id]/resolve/+server.ts
deleted file mode 100644
index 68ba6b74..00000000
--- a/libs/frontend/src/routes/api/moderation/report/[id]/resolve/+server.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-
-export async function POST({ params, request }: RequestEvent) {
- const { id } = params;
- const body = await request.json();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.moderationReportResolve(id)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST,
- body: JSON.stringify(body)
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/moderation/user/[id]/ban/[type]/+server.ts b/libs/frontend/src/routes/api/moderation/user/[id]/ban/[type]/+server.ts
deleted file mode 100644
index bcac1a97..00000000
--- a/libs/frontend/src/routes/api/moderation/user/[id]/ban/[type]/+server.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { env } from "$env/dynamic/private";
-import { error, json } from "@sveltejs/kit";
-import type { RequestHandler } from "./$types";
-import { backendUrls, httpRequestMethod, isBanType } from "types";
-
-export const POST: RequestHandler = async ({ request, params, cookies }) => {
- const sessionToken = cookies.get("sessionToken");
- const userId = params.id;
- const type = params.type;
-
- if (!sessionToken) {
- throw error(401, "Unauthorized");
- }
-
- if (!userId) {
- throw error(400, "User ID is required");
- }
-
- if (!isBanType(type)) {
- throw error(400, "Invalid ban type");
- }
-
- const body = await request.json();
- const { duration, reason } = body;
-
- try {
- const response = await fetch(
- backendUrls.moderationUserByIdBanByType(userId, type),
- {
- method: httpRequestMethod.POST,
- headers: {
- "Content-Type": "application/json",
- Cookie: `sessionToken=${sessionToken}`
- },
- body: JSON.stringify({ duration, reason })
- }
- );
-
- if (!response.ok) {
- const errorData = await response
- .json()
- .catch(() => ({ message: "Failed to ban user" }));
- throw error(response.status, errorData.message);
- }
-
- const result = await response.json();
- return json(result);
- } catch (err) {
- console.error("Error banning user:", err);
- if (err instanceof Error && "status" in err) {
- throw err;
- }
- throw error(500, "Internal server error");
- }
-};
diff --git a/libs/frontend/src/routes/api/moderation/user/[id]/ban/history/+server.ts b/libs/frontend/src/routes/api/moderation/user/[id]/ban/history/+server.ts
deleted file mode 100644
index 9a1ca749..00000000
--- a/libs/frontend/src/routes/api/moderation/user/[id]/ban/history/+server.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { error, json } from "@sveltejs/kit";
-import { apiUrls } from "@/config/api";
-import type { RequestHandler } from "./$types";
-
-export const GET: RequestHandler = async ({ params, cookies }) => {
- const sessionToken = cookies.get("sessionToken");
- const userId = params.id;
-
- if (!sessionToken) {
- throw error(401, "Unauthorized");
- }
-
- if (!userId) {
- throw error(400, "User ID is required");
- }
-
- try {
- const response = await fetch(apiUrls.moderationUserByIdBanHistory(userId), {
- method: "GET",
- headers: {
- Cookie: `sessionToken=${sessionToken}`
- }
- });
-
- if (!response.ok) {
- const errorData = await response
- .json()
- .catch(() => ({ message: "Failed to fetch ban history" }));
- throw error(response.status, errorData.message);
- }
-
- const result = await response.json();
- return json(result);
- } catch (err) {
- console.error("Error fetching ban history:", err);
- if (err instanceof Error && "status" in err) {
- throw err;
- }
- throw error(500, "Internal server error");
- }
-};
diff --git a/libs/frontend/src/routes/api/moderation/user/[id]/unban/+server.ts b/libs/frontend/src/routes/api/moderation/user/[id]/unban/+server.ts
deleted file mode 100644
index eb557a3e..00000000
--- a/libs/frontend/src/routes/api/moderation/user/[id]/unban/+server.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import { env } from "$env/dynamic/private";
-import { error, json } from "@sveltejs/kit";
-import type { RequestHandler } from "./$types";
-import { apiUrls } from "@/config/api";
-
-export const POST: RequestHandler = async ({ request, params, cookies }) => {
- const sessionToken = cookies.get("sessionToken");
- const userId = params.id;
-
- if (!sessionToken) {
- throw error(401, "Unauthorized");
- }
-
- if (!userId) {
- throw error(400, "User ID is required");
- }
-
- const body = await request.json();
- const { reason } = body;
-
- try {
- const response = await fetch(apiUrls.moderationUserByIdUnban(userId), {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Cookie: `sessionToken=${sessionToken}`
- },
- body: JSON.stringify({ reason })
- });
-
- if (!response.ok) {
- const errorData = await response
- .json()
- .catch(() => ({ message: "Failed to unban user" }));
- throw error(response.status, errorData.message);
- }
-
- const result = await response.json();
- return json(result);
- } catch (err) {
- console.error("Error unbanning user:", err);
- if (err instanceof Error && "status" in err) {
- throw err;
- }
- throw error(500, "Internal server error");
- }
-};
diff --git a/libs/frontend/src/routes/api/programming-languages/+server.ts b/libs/frontend/src/routes/api/programming-languages/+server.ts
deleted file mode 100644
index a1fe88d9..00000000
--- a/libs/frontend/src/routes/api/programming-languages/+server.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import { buildBackendUrl } from "@/config/backend";
-import type { RequestEvent } from "./$types";
-import { getCookieHeader } from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { json } from "@sveltejs/kit";
-
-export async function GET({ fetch, request }: RequestEvent) {
- const response = await fetch(
- buildBackendUrl(backendUrls.PROGRAMMING_LANGUAGE),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- }
- );
-
- const { languages } = await response.json();
-
- return json({ languages });
-}
diff --git a/libs/frontend/src/routes/api/puzzles/[id]/comment/+server.ts b/libs/frontend/src/routes/api/puzzles/[id]/comment/+server.ts
deleted file mode 100644
index 1545872f..00000000
--- a/libs/frontend/src/routes/api/puzzles/[id]/comment/+server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ params, request }: RequestEvent) {
- const body = await request.text();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.puzzleByIdComment(params.id)),
- {
- body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/submission/[id]/+server.ts b/libs/frontend/src/routes/api/submission/[id]/+server.ts
deleted file mode 100644
index 4c9b8d8e..00000000
--- a/libs/frontend/src/routes/api/submission/[id]/+server.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-import type { RequestEvent } from "./$types";
-
-export async function GET({ params, request }: RequestEvent) {
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.submissionById(params.id)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/submit-code/+server.ts b/libs/frontend/src/routes/api/submit-code/+server.ts
deleted file mode 100644
index 1ab10c84..00000000
--- a/libs/frontend/src/routes/api/submit-code/+server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ request }: RequestEvent) {
- const body = await request.text();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.SUBMISSION),
- {
- body: body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/submit-game/+server.ts b/libs/frontend/src/routes/api/submit-game/+server.ts
deleted file mode 100644
index b9d7e68c..00000000
--- a/libs/frontend/src/routes/api/submit-game/+server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { buildBackendUrl } from "@/config/backend";
-
-export async function POST({ request }: RequestEvent) {
- const body = await request.text();
-
- return await fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.SUBMISSION_GAME),
- {
- body: body,
- headers: getCookieHeader(request),
- method: httpRequestMethod.POST
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/supported-languages/+server.ts b/libs/frontend/src/routes/api/supported-languages/+server.ts
deleted file mode 100644
index 1f0005e6..00000000
--- a/libs/frontend/src/routes/api/supported-languages/+server.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { backendUrls, httpRequestMethod } from "types";
-import { buildBackendUrl } from "@/config/backend";
-import type { RequestEvent } from "./$types";
-import { getCookieHeader } from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { json } from "@sveltejs/kit";
-
-export async function GET({ fetch, request }: RequestEvent) {
- const response = await fetch(
- buildBackendUrl(backendUrls.PROGRAMMING_LANGUAGE),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- }
- );
-
- const { languages } = await response.json();
-
- const uniqueLanguages = Array.from(
- new Set(languages.map((lang: { language: string }) => lang.language))
- ).sort();
-
- return json({ languages: uniqueLanguages });
-}
diff --git a/libs/frontend/src/routes/api/user/[username]/+server.ts b/libs/frontend/src/routes/api/user/[username]/+server.ts
deleted file mode 100644
index 888b1272..00000000
--- a/libs/frontend/src/routes/api/user/[username]/+server.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-
-export async function GET({ params, request }: RequestEvent) {
- return fetchWithAuthenticationCookie(
- buildBackendUrl(backendUrls.userByUsername(params.username)),
- {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- }
- );
-}
diff --git a/libs/frontend/src/routes/api/user/[username]/puzzle/+server.ts b/libs/frontend/src/routes/api/user/[username]/puzzle/+server.ts
deleted file mode 100644
index fb97e7da..00000000
--- a/libs/frontend/src/routes/api/user/[username]/puzzle/+server.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import {
- fetchWithAuthenticationCookie,
- getCookieHeader
-} from "@/features/authentication/utils/fetch-with-authentication-cookie";
-import { backendUrls, httpRequestMethod } from "types";
-import type { RequestEvent } from "./$types";
-
-export async function GET({ params, request, url }: RequestEvent) {
- const username = params.username;
- const userPuzzlesByUsernameUrl = buildBackendUrl(
- backendUrls.userByUsernamePuzzle(username)
- );
-
- return fetchWithAuthenticationCookie(userPuzzlesByUsernameUrl + url.search, {
- headers: getCookieHeader(request),
- method: httpRequestMethod.GET
- });
-}
diff --git a/libs/frontend/src/routes/api/username-is-available/[username]/+server.ts b/libs/frontend/src/routes/api/username-is-available/[username]/+server.ts
deleted file mode 100644
index c827b212..00000000
--- a/libs/frontend/src/routes/api/username-is-available/[username]/+server.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { buildBackendUrl } from "@/config/backend";
-import { backendUrls } from "types";
-import type { RequestEvent } from "./$types";
-
-export async function GET({ fetch, params }: RequestEvent) {
- const username = params.username;
-
- return fetch(
- buildBackendUrl(backendUrls.userByUsernameIsAvailable(username))
- );
-}
diff --git a/libs/frontend/src/routes/leaderboards/+page.svelte b/libs/frontend/src/routes/leaderboards/+page.svelte
new file mode 100644
index 00000000..e10eda2b
--- /dev/null
+++ b/libs/frontend/src/routes/leaderboards/+page.svelte
@@ -0,0 +1,282 @@
+
+
+
+ Leaderboards
+
+
+
+ {#each Object.values(gameModeEnum) as mode}
+
+ {/each}
+
+
+
+ {#if loading}
+
+
+
+ {/if}
+
+
+ {#if error}
+
+
+ {error}
+
+
+ {/if}
+
+
+ {#if leaderboardData && !loading}
+
+
+ {gameModeNames[selectedMode]} Leaderboard
+
+ Last updated: {formatDate(leaderboardData.lastUpdated)}
+
+
+
+
+
+
+ Rank
+ Player
+ Rating
+ Games
+ Win Rate
+ Best Score
+ Avg Score
+
+
+
+ {#each leaderboardData.entries as entry}
+
+
+ {getRankBadge(entry.rank)}
+ #{entry.rank}
+
+
+
+ {entry.username}
+
+
+
+
+ {Math.round(entry.rating)}
+
+
+ (±{Math.round(entry.glicko.rd)})
+
+
+
+
+ {entry.gamesPlayed}
+ {entry.gamesWon}W
+
+
+
+
+
+
+ {(entry.winRate * 100).toFixed(1)}%
+
+
+
+
+ {Math.round(entry.bestScore).toLocaleString()}
+
+
+ {Math.round(entry.averageScore).toLocaleString()}
+
+
+ {/each}
+
+
+
+
+
+ Showing {(currentPage - 1) * pageSize + 1} to {Math.min(
+ currentPage * pageSize,
+ leaderboardData.totalEntries
+ )} of {leaderboardData.totalEntries} players
+
+
+
+
+ Page {currentPage} of {leaderboardData.totalPages}
+
+
+
+
+
+ {/if}
+
diff --git a/libs/frontend/src/routes/maintenance/+page.svelte b/libs/frontend/src/routes/maintenance/+page.svelte
index ff426dff..6ee100e5 100644
--- a/libs/frontend/src/routes/maintenance/+page.svelte
+++ b/libs/frontend/src/routes/maintenance/+page.svelte
@@ -3,7 +3,7 @@
import H1 from "@/components/typography/h1.svelte";
import P from "@/components/typography/p.svelte";
import Button from "@/components/ui/button/button.svelte";
- import { testIds } from "@/config/test-ids";
+ import { testIds } from "types";
diff --git a/libs/frontend/src/routes/moderation/+page.server.ts b/libs/frontend/src/routes/moderation/+page.server.ts
index 77d8cc18..6b726018 100644
--- a/libs/frontend/src/routes/moderation/+page.server.ts
+++ b/libs/frontend/src/routes/moderation/+page.server.ts
@@ -5,7 +5,9 @@ import {
} from "@/features/authentication/utils/fetch-with-authentication-cookie";
import {
backendUrls,
+ ERROR_MESSAGES,
httpRequestMethod,
+ PAGINATION_CONFIG,
reviewItemTypeEnum,
type ReviewItem
} from "types";
@@ -25,8 +27,10 @@ export async function load({ request, url }: PageServerLoadEvent) {
// Get query parameters
const type =
url.searchParams.get("type") || reviewItemTypeEnum.PENDING_PUZZLE;
- const page = url.searchParams.get("page") || "1";
- const limit = url.searchParams.get("limit") || "20";
+ const page =
+ url.searchParams.get("page") || String(PAGINATION_CONFIG.DEFAULT_PAGE);
+ const limit =
+ url.searchParams.get("limit") || String(PAGINATION_CONFIG.DEFAULT_LIMIT);
// Fetch review items from backend
const reviewUrl = `${buildBackendUrl(backendUrls.MODERATION_REVIEW)}?type=${type}&page=${page}&limit=${limit}`;
@@ -41,10 +45,15 @@ export async function load({ request, url }: PageServerLoadEvent) {
return {
reviewItems: {
data: [],
- pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }
+ pagination: {
+ page: PAGINATION_CONFIG.DEFAULT_PAGE,
+ limit: PAGINATION_CONFIG.DEFAULT_LIMIT,
+ total: 0,
+ totalPages: 0
+ }
} as PaginatedResponse,
currentType: type,
- error: "Failed to fetch review items"
+ error: ERROR_MESSAGES.MODERATION.FAILED_TO_FETCH_REVIEW_ITEMS
};
}
@@ -59,10 +68,15 @@ export async function load({ request, url }: PageServerLoadEvent) {
return {
reviewItems: {
data: [],
- pagination: { page: 1, limit: 20, total: 0, totalPages: 0 }
+ pagination: {
+ page: PAGINATION_CONFIG.DEFAULT_PAGE,
+ limit: PAGINATION_CONFIG.DEFAULT_LIMIT,
+ total: 0,
+ totalPages: 0
+ }
} as PaginatedResponse,
currentType: type,
- error: "Failed to fetch review items"
+ error: ERROR_MESSAGES.MODERATION.FAILED_TO_FETCH_REVIEW_ITEMS
};
}
}
diff --git a/libs/frontend/src/routes/moderation/+page.svelte b/libs/frontend/src/routes/moderation/+page.svelte
index 9d10685a..5c2da904 100644
--- a/libs/frontend/src/routes/moderation/+page.svelte
+++ b/libs/frontend/src/routes/moderation/+page.svelte
@@ -20,8 +20,9 @@
import { Button } from "#/ui/button";
import { page } from "$app/state";
import { formattedDateYearMonthDay } from "@/utils/date-functions";
- import { testIds } from "@/config/test-ids";
- import { apiUrls } from "@/config/api";
+ import { testIds } from "types";
+ import { buildBackendUrl } from "@/config/backend";
+ import { backendUrls } from "types";
import Pagination from "#/nav/pagination.svelte";
let { data }: { data: PageData } = $props();
@@ -79,9 +80,12 @@
async function handleApprove(id: string) {
try {
- const response = await fetch(apiUrls.moderationPuzzleByIdApprove(id), {
- method: httpRequestMethod.POST
- });
+ const response = await fetch(
+ buildBackendUrl(backendUrls.moderationPuzzleApprove(id)),
+ {
+ method: httpRequestMethod.POST
+ }
+ );
if (!response.ok) {
throw new Error("Failed to approve puzzle");
@@ -114,7 +118,7 @@
try {
const response = await fetch(
- apiUrls.moderationPuzzleByIdRevise(selectedPuzzleId),
+ buildBackendUrl(backendUrls.moderationPuzzleRevise(selectedPuzzleId)),
{
method: httpRequestMethod.POST,
headers: {
@@ -142,13 +146,16 @@
status: typeof reviewStatusEnum.RESOLVED | typeof reviewStatusEnum.REJECTED
) {
try {
- const response = await fetch(apiUrls.moderationReportByIdResolve(id), {
- method: httpRequestMethod.POST,
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({ status })
- });
+ const response = await fetch(
+ buildBackendUrl(backendUrls.moderationReportResolve(id)),
+ {
+ method: httpRequestMethod.POST,
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({ status })
+ }
+ );
if (!response.ok) {
throw new Error("Failed to resolve report");
@@ -184,7 +191,9 @@
try {
const response = await fetch(
- apiUrls.moderationUserByIdBanByType(selectedUserId, banType),
+ buildBackendUrl(
+ backendUrls.moderationUserByIdBanByType(selectedUserId, banType)
+ ),
{
method: httpRequestMethod.POST,
headers: {
@@ -219,15 +228,18 @@
}
try {
- const response = await fetch(apiUrls.moderationUserByIdUnban(userId), {
- method: httpRequestMethod.POST,
- headers: {
- "Content-Type": "application/json"
- },
- body: JSON.stringify({
- reason: "Unbanned by moderator"
- })
- });
+ const response = await fetch(
+ buildBackendUrl(backendUrls.moderationUserByIdUnban(userId)),
+ {
+ method: httpRequestMethod.POST,
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify({
+ reason: "Unbanned by moderator"
+ })
+ }
+ );
if (!response.ok) {
throw new Error("Failed to unban user");
@@ -249,7 +261,7 @@
try {
const response = await fetch(
- apiUrls.moderationUserByIdBanHistory(userId),
+ buildBackendUrl(backendUrls.moderationUserByIdBanHistory(userId)),
{
method: httpRequestMethod.GET
}
diff --git a/libs/frontend/src/routes/profile/[username]/+page.server.ts b/libs/frontend/src/routes/profile/[username]/+page.server.ts
index 0ae90207..d717ff4f 100644
--- a/libs/frontend/src/routes/profile/[username]/+page.server.ts
+++ b/libs/frontend/src/routes/profile/[username]/+page.server.ts
@@ -1,11 +1,19 @@
-import { getUserActivityByUsername } from "../../api/get-user-activity-by-username.js";
-import { activityTypeEnum, type PuzzleDto, type SubmissionDto } from "types";
+import {
+ activityTypeEnum,
+ backendUrls,
+ type PuzzleDto,
+ type SubmissionDto
+} from "types";
import type { PageServerLoadEvent } from "./$types";
+import { buildBackendUrl } from "@/config/backend";
+import { fetchWithAuthenticationCookie } from "@/features/authentication/utils/fetch-with-authentication-cookie";
export async function load({ params }: PageServerLoadEvent) {
const username = params.username;
- const response = await getUserActivityByUsername(username);
+ const response = await fetchWithAuthenticationCookie(
+ buildBackendUrl(backendUrls.userByUsernameActivity(username))
+ );
if (!response.ok) {
console.error(response);
}
diff --git a/libs/frontend/src/routes/profile/[username]/+page.svelte b/libs/frontend/src/routes/profile/[username]/+page.svelte
index 823da1bd..971642a5 100644
--- a/libs/frontend/src/routes/profile/[username]/+page.svelte
+++ b/libs/frontend/src/routes/profile/[username]/+page.svelte
@@ -5,7 +5,7 @@
import H1 from "@/components/typography/h1.svelte";
import * as Card from "@/components/ui/card";
import Container from "@/components/ui/container/container.svelte";
- import { testIds } from "@/config/test-ids";
+ import { testIds } from "types";
import ActivityGroup from "@/features/profile/components/activity-group.svelte";
import ActivityHeatmap from "@/features/profile/components/activity-heatmap.svelte";
import dayjs from "dayjs";
diff --git a/libs/frontend/src/routes/profile/[username]/puzzles/+page.server.ts b/libs/frontend/src/routes/profile/[username]/puzzles/+page.server.ts
index 20bada8b..3ca37d15 100644
--- a/libs/frontend/src/routes/profile/[username]/puzzles/+page.server.ts
+++ b/libs/frontend/src/routes/profile/[username]/puzzles/+page.server.ts
@@ -1,6 +1,7 @@
import type { PageServerLoadEvent } from "./$types";
import { httpRequestMethod, type PaginatedQueryResponse } from "types";
-import { apiUrls } from "@/config/api";
+import { buildBackendUrl } from "@/config/backend";
+import { backendUrls } from "types";
export async function load({
fetch,
@@ -10,7 +11,7 @@ export async function load({
}: PageServerLoadEvent) {
const username = params.username;
- const apiUrl = apiUrls.userByUsernamePuzzle(username);
+ const apiUrl = buildBackendUrl(backendUrls.userByUsernamePuzzle(username));
const apiUrlWithQueryParams = new URL(apiUrl, request.url);
apiUrlWithQueryParams.search = url.search;
diff --git a/libs/frontend/src/routes/profile/[username]/puzzles/+page.svelte b/libs/frontend/src/routes/profile/[username]/puzzles/+page.svelte
index 27efdf85..aa9f3554 100644
--- a/libs/frontend/src/routes/profile/[username]/puzzles/+page.svelte
+++ b/libs/frontend/src/routes/profile/[username]/puzzles/+page.svelte
@@ -14,7 +14,7 @@
import LogicalUnit from "@/components/ui/logical-unit/logical-unit.svelte";
import PuzzleDifficultyBadge from "@/features/puzzles/components/puzzle-difficulty-badge.svelte";
import PuzzleVisibilityBadge from "@/features/puzzles/components/puzzle-visibility-badge.svelte";
- import { testIds } from "@/config/test-ids";
+ import { testIds } from "types";
import { authenticatedUserInfo } from "@/stores/index.js";
let { data }: { data: PaginatedQueryResponse | undefined } = $props();
diff --git a/libs/frontend/src/routes/puzzles/+page.svelte b/libs/frontend/src/routes/puzzles/+page.svelte
index 4758e823..0c283b9a 100644
--- a/libs/frontend/src/routes/puzzles/+page.svelte
+++ b/libs/frontend/src/routes/puzzles/+page.svelte
@@ -12,7 +12,7 @@
import LogicalUnit from "@/components/ui/logical-unit/logical-unit.svelte";
import PuzzleDifficultyBadge from "@/features/puzzles/components/puzzle-difficulty-badge.svelte";
import PuzzleVisibilityBadge from "@/features/puzzles/components/puzzle-visibility-badge.svelte";
- import { testIds } from "@/config/test-ids";
+ import { testIds } from "types";
import { authenticatedUserInfo, isAuthenticated } from "@/stores";
let { data }: { data: PaginatedQueryResponse | undefined } = $props();
diff --git a/libs/frontend/src/routes/puzzles/[id]/+page.svelte b/libs/frontend/src/routes/puzzles/[id]/+page.svelte
index 53125e15..4546a033 100644
--- a/libs/frontend/src/routes/puzzles/[id]/+page.svelte
+++ b/libs/frontend/src/routes/puzzles/[id]/+page.svelte
@@ -20,7 +20,7 @@
import H2 from "@/components/typography/h2.svelte";
import Comments from "@/features/comment/components/comments.svelte";
import AddCommentForm from "@/features/comment/components/add-comment-form.svelte";
- import { testIds } from "@/config/test-ids";
+ import { testIds } from "types";
import { page } from "$app/state";
let { data } = $props();
diff --git a/libs/types/src/core/api/schema/account.schema.ts b/libs/types/src/core/api/schema/account.schema.ts
new file mode 100644
index 00000000..3a1b25a1
--- /dev/null
+++ b/libs/types/src/core/api/schema/account.schema.ts
@@ -0,0 +1,48 @@
+import { z } from "zod";
+import { preferencesDtoSchema } from "../../preferences/schema/preferences-dto.schema.js";
+import { preferencesEntitySchema } from "../../preferences/schema/preferences-entity.schema.js";
+import { messageSchema } from "../../common/schema/message.schema.js";
+
+// GET /account/preferences response
+export const getPreferencesResponseSchema = preferencesDtoSchema;
+export type GetPreferencesResponse = z.infer<
+ typeof getPreferencesResponseSchema
+>;
+
+// POST /account/preferences request
+export const createPreferencesRequestSchema = preferencesEntitySchema;
+export type CreatePreferencesRequest = z.infer<
+ typeof createPreferencesRequestSchema
+>;
+
+// POST /account/preferences response
+export const createPreferencesResponseSchema = z.object({
+ message: messageSchema,
+ preferences: preferencesDtoSchema,
+});
+export type CreatePreferencesResponse = z.infer<
+ typeof createPreferencesResponseSchema
+>;
+
+// PUT /account/preferences request
+export const updatePreferencesRequestSchema = preferencesEntitySchema.partial();
+export type UpdatePreferencesRequest = z.infer<
+ typeof updatePreferencesRequestSchema
+>;
+
+// PUT /account/preferences response
+export const updatePreferencesResponseSchema = z.object({
+ message: messageSchema,
+ preferences: preferencesDtoSchema,
+});
+export type UpdatePreferencesResponse = z.infer<
+ typeof updatePreferencesResponseSchema
+>;
+
+// DELETE /account/preferences response
+export const deletePreferencesResponseSchema = z.object({
+ message: messageSchema,
+});
+export type DeletePreferencesResponse = z.infer<
+ typeof deletePreferencesResponseSchema
+>;
diff --git a/libs/types/src/core/api/schema/auth/login.schema.ts b/libs/types/src/core/api/schema/auth/login.schema.ts
new file mode 100644
index 00000000..a428620a
--- /dev/null
+++ b/libs/types/src/core/api/schema/auth/login.schema.ts
@@ -0,0 +1,15 @@
+import { z } from "zod";
+import { messageSchema } from "../../../common/schema/message.schema.js";
+
+/**
+ * POST /login - User login
+ */
+export const loginRequestSchema = z.object({
+ identifier: z.string().min(1, "Identifier is required"),
+ password: z.string().min(1, "Password is required"),
+});
+
+export const loginResponseSchema = messageSchema;
+
+export type LoginRequest = z.infer;
+export type LoginResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/auth/logout.schema.ts b/libs/types/src/core/api/schema/auth/logout.schema.ts
new file mode 100644
index 00000000..6b4cf51c
--- /dev/null
+++ b/libs/types/src/core/api/schema/auth/logout.schema.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+import { messageSchema } from "../../../common/schema/message.schema.js";
+
+/**
+ * POST /logout - User logout
+ */
+export const logoutResponseSchema = messageSchema;
+
+export type LogoutResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/auth/register.schema.ts b/libs/types/src/core/api/schema/auth/register.schema.ts
new file mode 100644
index 00000000..e2f63b51
--- /dev/null
+++ b/libs/types/src/core/api/schema/auth/register.schema.ts
@@ -0,0 +1,33 @@
+import { z } from "zod";
+import { messageSchema } from "../../../common/schema/message.schema.js";
+import { USERNAME_CONFIG } from "../../../authentication/config/username-config.js";
+import { PASSWORD_CONFIG } from "../../../authentication/config/password-config.js";
+
+export const registerRequestSchema = z.object({
+ username: z
+ .string()
+ .min(
+ USERNAME_CONFIG.minUsernameLength,
+ `Username must be at least ${USERNAME_CONFIG.minUsernameLength} characters`,
+ )
+ .max(
+ USERNAME_CONFIG.maxUsernameLength,
+ `Username cannot exceed ${USERNAME_CONFIG.maxUsernameLength} characters`,
+ )
+ .regex(
+ USERNAME_CONFIG.allowedCharacters,
+ "Username can only contain letters, numbers, underscores, and hyphens",
+ ),
+ email: z.string().email("Invalid email address").optional(),
+ password: z
+ .string()
+ .min(
+ PASSWORD_CONFIG.minPasswordLength,
+ `Password must be at least ${PASSWORD_CONFIG.minPasswordLength} characters`,
+ ),
+});
+
+export const registerResponseSchema = messageSchema;
+
+export type RegisterRequest = z.infer;
+export type RegisterResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/comment.schema.ts b/libs/types/src/core/api/schema/comment.schema.ts
new file mode 100644
index 00000000..9a73dbe4
--- /dev/null
+++ b/libs/types/src/core/api/schema/comment.schema.ts
@@ -0,0 +1,40 @@
+import { z } from "zod";
+import { commentDtoSchema } from "../../comment/schema/comment-dto.schema.js";
+import { createCommentSchema } from "../../comment/schema/create-comment.schema.js";
+import { commentVoteRequestSchema } from "../../comment/schema/comment-vote.schema.js";
+import { messageSchema } from "../../common/schema/message.schema.js";
+
+// GET /comment/:id response
+export const getCommentByIdResponseSchema = commentDtoSchema;
+export type GetCommentByIdResponse = z.infer<
+ typeof getCommentByIdResponseSchema
+>;
+
+// DELETE /comment/:id response
+export const deleteCommentResponseSchema = z.object({
+ message: messageSchema,
+});
+export type DeleteCommentResponse = z.infer;
+
+// POST /comment/:id/comment request
+export const createReplyCommentRequestSchema = createCommentSchema;
+export type CreateReplyCommentRequest = z.infer<
+ typeof createReplyCommentRequestSchema
+>;
+
+// POST /comment/:id/comment response
+export const createReplyCommentResponseSchema = commentDtoSchema;
+export type CreateReplyCommentResponse = z.infer<
+ typeof createReplyCommentResponseSchema
+>;
+
+// POST /comment/:id/vote request
+export const voteCommentRequestSchema = commentVoteRequestSchema;
+export type VoteCommentRequest = z.infer;
+
+// POST /comment/:id/vote response
+export const voteCommentResponseSchema = z.object({
+ message: messageSchema,
+ voteCount: z.number(),
+});
+export type VoteCommentResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/execute-code.schema.ts b/libs/types/src/core/api/schema/execute-code.schema.ts
new file mode 100644
index 00000000..50c91212
--- /dev/null
+++ b/libs/types/src/core/api/schema/execute-code.schema.ts
@@ -0,0 +1,11 @@
+import { z } from "zod";
+import { pistonExecutionRequestSchema } from "../../piston/schema/request.js";
+import { codeExecutionResponseSchema } from "../../piston/schema/code-execution-response.js";
+
+// Request schema for code execution
+export const executeCodeRequestSchema = pistonExecutionRequestSchema;
+export type ExecuteCodeRequest = z.infer;
+
+// Response schema for code execution
+export const executeCodeResponseSchema = codeExecutionResponseSchema;
+export type ExecuteCodeResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/execute/execute-api.schema.ts b/libs/types/src/core/api/schema/execute/execute-api.schema.ts
new file mode 100644
index 00000000..edc4ca92
--- /dev/null
+++ b/libs/types/src/core/api/schema/execute/execute-api.schema.ts
@@ -0,0 +1,21 @@
+import { z } from "zod";
+import { pistonExecutionRequestSchema } from "../../../piston/schema/request.js";
+import { codeExecutionResponseSchema } from "../../../piston/schema/code-execution-response.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+
+/**
+ * POST /execute - Execute code without saving submission
+ * Used for testing code before final submission
+ */
+export const executeCodeRequestSchema = z.object({
+ code: z.string().min(1, "Code cannot be empty"),
+ language: z.string().min(1, "Language is required"),
+ testInput: z.string().default(""),
+ testOutput: z.string().default(""),
+});
+
+export const executeCodeResponseSchema =
+ codeExecutionResponseSchema.or(errorResponseSchema);
+
+export type ExecuteCodeRequest = z.infer;
+export type ExecuteCodeResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/game/game-api.schema.ts b/libs/types/src/core/api/schema/game/game-api.schema.ts
new file mode 100644
index 00000000..10894bed
--- /dev/null
+++ b/libs/types/src/core/api/schema/game/game-api.schema.ts
@@ -0,0 +1,118 @@
+import { z } from "zod";
+import { objectIdSchema } from "../../../common/schema/object-id.js";
+import { gameEntitySchema } from "../../../game/schema/game-entity.schema.js";
+import { gameModeSchema } from "../../../game/schema/mode.schema.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+import { gameVisibilitySchema } from "../../../game/schema/visibility.schema.js";
+import { MINIMUM_PLAYERS_IN_GAME } from "../../../game/config/game-config.js";
+
+/**
+ * POST /game - Create a new multiplayer game
+ */
+export const createGameRequestSchema = z.object({
+ puzzleId: objectIdSchema.optional(),
+ mode: gameModeSchema,
+ visibility: gameVisibilitySchema,
+ maxPlayers: z.number().int().min(MINIMUM_PLAYERS_IN_GAME),
+ timeLimit: z.number().int().min(60).max(3600).optional(), // in seconds
+});
+
+export const createGameResponseSchema = gameEntitySchema
+ .extend({
+ inviteCode: z.string().optional(), // For private games
+ })
+ .or(errorResponseSchema);
+
+export type CreateGameRequest = z.infer;
+export type CreateGameResponse = z.infer;
+
+/**
+ * GET /game/:id - Get game details
+ */
+export const getGameByIdRequestSchema = z.object({
+ id: objectIdSchema,
+});
+
+export const getGameByIdResponseSchema =
+ gameEntitySchema.or(errorResponseSchema);
+
+export type GetGameByIdRequest = z.infer;
+export type GetGameByIdResponse = z.infer;
+
+/**
+ * GET /game - List available games
+ */
+export const listGamesRequestSchema = z.object({
+ visibility: gameVisibilitySchema.optional(),
+ mode: gameModeSchema.optional(),
+ status: z.enum(["waiting", "in_progress", "completed"]).optional(),
+ page: z.number().int().positive().default(1),
+ pageSize: z.number().int().positive().max(50).default(20),
+});
+
+export const listGamesResponseSchema = z
+ .object({
+ items: z.array(gameEntitySchema),
+ page: z.number().int().positive(),
+ pageSize: z.number().int().positive(),
+ totalPages: z.number().int().nonnegative(),
+ totalItems: z.number().int().nonnegative(),
+ })
+ .or(errorResponseSchema);
+
+export type ListGamesRequest = z.infer;
+export type ListGamesResponse = z.infer;
+
+/**
+ * POST /game/:id/join - Join a game
+ */
+export const joinGameRequestSchema = z.object({
+ gameId: objectIdSchema,
+ inviteCode: z.string().optional(),
+});
+
+export const joinGameResponseSchema = z
+ .object({
+ success: z.boolean(),
+ message: z.string(),
+ game: gameEntitySchema.optional(),
+ })
+ .or(errorResponseSchema);
+
+export type JoinGameRequest = z.infer;
+export type JoinGameResponse = z.infer;
+
+/**
+ * POST /game/:id/leave - Leave a game
+ */
+export const leaveGameRequestSchema = z.object({
+ gameId: objectIdSchema,
+});
+
+export const leaveGameResponseSchema = z
+ .object({
+ success: z.boolean(),
+ message: z.string(),
+ })
+ .or(errorResponseSchema);
+
+export type LeaveGameRequest = z.infer;
+export type LeaveGameResponse = z.infer;
+
+/**
+ * POST /game/:id/start - Start a game (host only)
+ */
+export const startGameRequestSchema = z.object({
+ gameId: objectIdSchema,
+});
+
+export const startGameResponseSchema = z
+ .object({
+ success: z.boolean(),
+ message: z.string(),
+ startTime: z.date().or(z.string()).optional(),
+ })
+ .or(errorResponseSchema);
+
+export type StartGameRequest = z.infer;
+export type StartGameResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/game/leaderboard-api.schema.ts b/libs/types/src/core/api/schema/game/leaderboard-api.schema.ts
new file mode 100644
index 00000000..08db8c74
--- /dev/null
+++ b/libs/types/src/core/api/schema/game/leaderboard-api.schema.ts
@@ -0,0 +1,68 @@
+import { z } from "zod";
+import { objectIdSchema } from "../../../common/schema/object-id.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+import { gameModeSchema } from "../../../game/schema/mode.schema.js";
+import { acceptedDateSchema } from "../../../common/schema/accepted-date.js";
+
+/**
+ * GET /game/:id/leaderboard - Get ranked leaderboard for a game
+ */
+export const getGameLeaderboardRequestSchema = z.object({
+ gameId: objectIdSchema,
+});
+
+export const getGameLeaderboardResponseSchema = z
+ .object({
+ gameId: objectIdSchema,
+ mode: gameModeSchema,
+ leaderboard: z.array(
+ z.object({
+ userId: objectIdSchema,
+ username: z.string(),
+ score: z.number(),
+ timeSpent: z.number(), // in seconds
+ codeLength: z.number().int().nonnegative().optional(),
+ successRate: z.number().min(0).max(1),
+ rank: z.number().int().positive(),
+ programmingLanguage: z.string().optional(),
+ }),
+ ),
+ totalPlayers: z.number().int().nonnegative(),
+ })
+ .or(errorResponseSchema);
+
+export type GetGameLeaderboardRequest = z.infer<
+ typeof getGameLeaderboardRequestSchema
+>;
+export type GetGameLeaderboardResponse = z.infer<
+ typeof getGameLeaderboardResponseSchema
+>;
+
+/**
+ * GET /game/:id/stats - Get game statistics and metadata
+ */
+export const getGameStatsRequestSchema = z.object({
+ gameId: objectIdSchema,
+});
+
+export const getGameStatsResponseSchema = z
+ .object({
+ gameId: objectIdSchema,
+ mode: gameModeSchema,
+ description: z.string(),
+ displayMetrics: z.array(z.string()),
+ playerCount: z.number().int().nonnegative(),
+ submissionCount: z.number().int().nonnegative(),
+ createdAt: acceptedDateSchema,
+ options: z
+ .object({
+ mode: gameModeSchema,
+ maxPlayers: z.number().int().positive(),
+ timeLimit: z.number().int().positive().optional(),
+ })
+ .optional(),
+ })
+ .or(errorResponseSchema);
+
+export type GetGameStatsRequest = z.infer;
+export type GetGameStatsResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/leaderboard/leaderboard-api.schema.ts b/libs/types/src/core/api/schema/leaderboard/leaderboard-api.schema.ts
new file mode 100644
index 00000000..8e01c12d
--- /dev/null
+++ b/libs/types/src/core/api/schema/leaderboard/leaderboard-api.schema.ts
@@ -0,0 +1,60 @@
+import { z } from "zod";
+import { gameModeSchema } from "../../../game/schema/mode.schema.js";
+import { leaderboardEntrySchema } from "../../../leaderboard/schema/leaderboard-entry.schema.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+
+/**
+ * GET /leaderboard/:gameMode - Get leaderboard for a specific game mode
+ */
+export const getLeaderboardRequestSchema = z.object({
+ gameMode: gameModeSchema,
+ page: z.number().int().positive().default(1),
+ pageSize: z.number().int().positive().max(100).default(50),
+});
+
+export const getLeaderboardResponseSchema = z
+ .object({
+ gameMode: gameModeSchema,
+ entries: z.array(leaderboardEntrySchema),
+ page: z.number().int().positive(),
+ pageSize: z.number().int().positive(),
+ totalEntries: z.number().int().nonnegative(),
+ totalPages: z.number().int().nonnegative(),
+ lastUpdated: z.date().or(z.string()),
+ })
+ .or(errorResponseSchema);
+
+export type GetLeaderboardRequest = z.infer;
+export type GetLeaderboardResponse = z.infer<
+ typeof getLeaderboardResponseSchema
+>;
+
+/**
+ * GET /leaderboard/user/:userId - Get user's rankings across all game modes
+ */
+export const getUserLeaderboardStatsRequestSchema = z.object({
+ userId: z.string(),
+});
+
+export const getUserLeaderboardStatsResponseSchema = z
+ .object({
+ userId: z.string(),
+ username: z.string(),
+ rankings: z.record(
+ z.string(), // Game mode as string key
+ z.object({
+ rank: z.number().int().positive().optional(),
+ rating: z.number(),
+ gamesPlayed: z.number().int().nonnegative(),
+ winRate: z.number().min(0).max(1),
+ }),
+ ),
+ })
+ .or(errorResponseSchema);
+
+export type GetUserLeaderboardStatsRequest = z.infer<
+ typeof getUserLeaderboardStatsRequestSchema
+>;
+export type GetUserLeaderboardStatsResponse = z.infer<
+ typeof getUserLeaderboardStatsResponseSchema
+>;
diff --git a/libs/types/src/core/api/schema/moderation.schema.ts b/libs/types/src/core/api/schema/moderation.schema.ts
new file mode 100644
index 00000000..b6199785
--- /dev/null
+++ b/libs/types/src/core/api/schema/moderation.schema.ts
@@ -0,0 +1,101 @@
+import { z } from "zod";
+import { messageSchema } from "../../common/schema/message.schema.js";
+import { paginatedQueryResponseSchema } from "../../common/schema/paginated-query-response.schema.js";
+import { paginatedQuerySchema } from "../../common/schema/paginated-query.schema.js";
+import { reviewItemSchema } from "../../moderation/schema/review-item.schema.js";
+import {
+ approvePuzzleSchema,
+ revisePuzzleSchema,
+} from "../../moderation/schema/puzzle-moderation.schema.js";
+import { reportEntitySchema } from "../../moderation/schema/report.schema.js";
+import { userBanEntitySchema } from "../../moderation/schema/user-ban.schema.js";
+
+// GET /moderation/review query params
+export const getModerationReviewQuerySchema = paginatedQuerySchema;
+export type GetModerationReviewQuery = z.infer<
+ typeof getModerationReviewQuerySchema
+>;
+
+// GET /moderation/review response
+export const getModerationReviewResponseSchema =
+ paginatedQueryResponseSchema.extend({
+ items: z.array(reviewItemSchema),
+ });
+export type GetModerationReviewResponse = z.infer<
+ typeof getModerationReviewResponseSchema
+>;
+
+// POST /moderation/puzzle/:id/approve request
+export const approvePuzzleRequestSchema = approvePuzzleSchema;
+export type ApprovePuzzleRequest = z.infer;
+
+// POST /moderation/puzzle/:id/approve response
+export const approvePuzzleResponseSchema = z.object({
+ message: messageSchema,
+});
+export type ApprovePuzzleResponse = z.infer;
+
+// POST /moderation/puzzle/:id/revise request
+export const revisePuzzleRequestSchema = revisePuzzleSchema;
+export type RevisePuzzleRequest = z.infer;
+
+// POST /moderation/puzzle/:id/revise response
+export const revisePuzzleResponseSchema = z.object({
+ message: messageSchema,
+});
+export type RevisePuzzleResponse = z.infer;
+
+// POST /moderation/report/:id/resolve request
+export const resolveReportRequestSchema = z.object({
+ action: z.enum(["accept", "reject"]),
+ notes: z.string().optional(),
+});
+export type ResolveReportRequest = z.infer;
+
+// POST /moderation/report/:id/resolve response
+export const resolveReportResponseSchema = z.object({
+ message: messageSchema,
+});
+export type ResolveReportResponse = z.infer;
+
+// POST /moderation/user/:id/ban/:type request
+export const banUserRequestSchema = z.object({
+ reason: z.string().min(10, "Reason must be at least 10 characters"),
+ duration: z.number().positive().optional(), // Duration in seconds, optional for permanent bans
+});
+export type BanUserRequest = z.infer;
+
+// POST /moderation/user/:id/ban/:type response
+export const banUserResponseSchema = z.object({
+ message: messageSchema,
+ ban: userBanEntitySchema,
+});
+export type BanUserResponse = z.infer;
+
+// GET /moderation/user/:id/ban/history response
+export const getBanHistoryResponseSchema = z.object({
+ bans: z.array(userBanEntitySchema),
+});
+export type GetBanHistoryResponse = z.infer;
+
+// POST /moderation/user/:id/unban response
+export const unbanUserResponseSchema = z.object({
+ message: messageSchema,
+});
+export type UnbanUserResponse = z.infer;
+
+// POST /report request
+export const createReportRequestSchema = reportEntitySchema.omit({
+ createdAt: true,
+ updatedAt: true,
+ status: true,
+ resolvedBy: true,
+});
+export type CreateReportRequest = z.infer;
+
+// POST /report response
+export const createReportResponseSchema = z.object({
+ message: messageSchema,
+ report: reportEntitySchema,
+});
+export type CreateReportResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/programming-language.schema.ts b/libs/types/src/core/api/schema/programming-language.schema.ts
new file mode 100644
index 00000000..4feb6a60
--- /dev/null
+++ b/libs/types/src/core/api/schema/programming-language.schema.ts
@@ -0,0 +1,26 @@
+import { z } from "zod";
+import { programmingLanguageDtoSchema } from "../../programming-language/schema/programming-language-dto.schema.js";
+import { paginatedQueryResponseSchema } from "../../common/schema/paginated-query-response.schema.js";
+
+// GET /programming-language response
+export const programmingLanguagesResponseSchema = z.object({
+ languages: z.array(programmingLanguageDtoSchema),
+});
+export type ProgrammingLanguagesResponse = z.infer<
+ typeof programmingLanguagesResponseSchema
+>;
+
+// GET /programming-language/:id response
+export const programmingLanguageByIdResponseSchema =
+ programmingLanguageDtoSchema;
+export type ProgrammingLanguageByIdResponse = z.infer<
+ typeof programmingLanguageByIdResponseSchema
+>;
+
+// GET /programming-language/supported (filtered unique languages)
+export const supportedLanguagesResponseSchema = z.object({
+ languages: z.array(z.string()),
+});
+export type SupportedLanguagesResponse = z.infer<
+ typeof supportedLanguagesResponseSchema
+>;
diff --git a/libs/types/src/core/api/schema/programming-language/programming-language-api.schema.ts b/libs/types/src/core/api/schema/programming-language/programming-language-api.schema.ts
new file mode 100644
index 00000000..b0c7bb39
--- /dev/null
+++ b/libs/types/src/core/api/schema/programming-language/programming-language-api.schema.ts
@@ -0,0 +1,32 @@
+import { z } from "zod";
+import { programmingLanguageDtoSchema } from "../../../programming-language/schema/programming-language-dto.schema.js";
+import { objectIdSchema } from "../../../common/schema/object-id.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+
+/**
+ * GET /programming-language - List all available programming languages
+ */
+export const getProgrammingLanguagesResponseSchema = z.array(
+ programmingLanguageDtoSchema,
+);
+
+export type GetProgrammingLanguagesResponse = z.infer<
+ typeof getProgrammingLanguagesResponseSchema
+>;
+
+/**
+ * GET /programming-language/:id - Get programming language by ID
+ */
+export const getProgrammingLanguageByIdRequestSchema = z.object({
+ id: objectIdSchema,
+});
+
+export const getProgrammingLanguageByIdResponseSchema =
+ programmingLanguageDtoSchema.or(errorResponseSchema);
+
+export type GetProgrammingLanguageByIdRequest = z.infer<
+ typeof getProgrammingLanguageByIdRequestSchema
+>;
+export type GetProgrammingLanguageByIdResponse = z.infer<
+ typeof getProgrammingLanguageByIdResponseSchema
+>;
diff --git a/libs/types/src/core/api/schema/puzzle.schema.ts b/libs/types/src/core/api/schema/puzzle.schema.ts
new file mode 100644
index 00000000..cb365d23
--- /dev/null
+++ b/libs/types/src/core/api/schema/puzzle.schema.ts
@@ -0,0 +1,63 @@
+import { z } from "zod";
+import { puzzleDtoSchema } from "../../puzzle/schema/puzzle-dto.schema.js";
+import { paginatedQueryResponseSchema } from "../../common/schema/paginated-query-response.schema.js";
+import { paginatedQuerySchema } from "../../common/schema/paginated-query.schema.js";
+import { commentDtoSchema } from "../../comment/schema/comment-dto.schema.js";
+import { createCommentSchema } from "../../comment/schema/create-comment.schema.js";
+import { solutionSchema } from "../../puzzle/schema/solution.schema.js";
+import { messageSchema } from "../../common/schema/message.schema.js";
+
+// GET /puzzle query params
+export const getPuzzlesQuerySchema = paginatedQuerySchema;
+export type GetPuzzlesQuery = z.infer;
+
+// GET /puzzle response
+export const getPuzzlesResponseSchema = paginatedQueryResponseSchema.extend({
+ items: z.array(puzzleDtoSchema),
+});
+export type GetPuzzlesResponse = z.infer;
+
+// GET /puzzle/:id response
+export const getPuzzleByIdResponseSchema = puzzleDtoSchema;
+export type GetPuzzleByIdResponse = z.infer;
+
+// POST /puzzle/:id/comment request
+export const createPuzzleCommentRequestSchema = createCommentSchema;
+export type CreatePuzzleCommentRequest = z.infer<
+ typeof createPuzzleCommentRequestSchema
+>;
+
+// POST /puzzle/:id/comment response
+export const createPuzzleCommentResponseSchema = commentDtoSchema;
+export type CreatePuzzleCommentResponse = z.infer<
+ typeof createPuzzleCommentResponseSchema
+>;
+
+// GET /puzzle/:id/comment query params
+export const getPuzzleCommentsQuerySchema = paginatedQuerySchema;
+export type GetPuzzleCommentsQuery = z.infer<
+ typeof getPuzzleCommentsQuerySchema
+>;
+
+// GET /puzzle/:id/comment response
+export const getPuzzleCommentsResponseSchema =
+ paginatedQueryResponseSchema.extend({
+ items: z.array(commentDtoSchema),
+ });
+export type GetPuzzleCommentsResponse = z.infer<
+ typeof getPuzzleCommentsResponseSchema
+>;
+
+// GET /puzzle/:id/solution response
+export const getPuzzleSolutionResponseSchema = z.object({
+ solutions: z.array(solutionSchema),
+});
+export type GetPuzzleSolutionResponse = z.infer<
+ typeof getPuzzleSolutionResponseSchema
+>;
+
+// DELETE /puzzle/:id response
+export const deletePuzzleResponseSchema = z.object({
+ message: messageSchema,
+});
+export type DeletePuzzleResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/puzzle/puzzle-api.schema.ts b/libs/types/src/core/api/schema/puzzle/puzzle-api.schema.ts
new file mode 100644
index 00000000..0ae80ca5
--- /dev/null
+++ b/libs/types/src/core/api/schema/puzzle/puzzle-api.schema.ts
@@ -0,0 +1,55 @@
+import { z } from "zod";
+import { paginatedQuerySchema } from "../../../common/schema/paginated-query.schema.js";
+import { paginatedQueryResponseSchema } from "../../../common/schema/paginated-query-response.schema.js";
+import { puzzleEntitySchema } from "../../../puzzle/schema/puzzle-entity.schema.js";
+import { objectIdSchema } from "../../../common/schema/object-id.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+
+/**
+ * GET /puzzle - List puzzles with pagination
+ */
+export const getPuzzlesRequestSchema = paginatedQuerySchema;
+
+export const getPuzzlesResponseSchema = paginatedQueryResponseSchema.extend({
+ items: z.array(puzzleEntitySchema),
+});
+
+export type GetPuzzlesRequest = z.infer;
+export type GetPuzzlesResponse = z.infer;
+
+/**
+ * GET /puzzle/:id - Get single puzzle by ID
+ */
+export const getPuzzleByIdRequestSchema = z.object({
+ id: objectIdSchema,
+});
+
+export const getPuzzleByIdResponseSchema =
+ puzzleEntitySchema.or(errorResponseSchema);
+
+export type GetPuzzleByIdRequest = z.infer;
+export type GetPuzzleByIdResponse = z.infer;
+
+/**
+ * POST /puzzle - Create new puzzle
+ */
+export const createPuzzleRequestSchema = z.object({
+ title: z.string().min(1).max(200),
+ description: z.string().min(1),
+ difficulty: z.enum(["easy", "medium", "hard"]),
+ validators: z
+ .array(
+ z.object({
+ input: z.string(),
+ output: z.string(),
+ }),
+ )
+ .min(1),
+ tags: z.array(z.string()).optional(),
+});
+
+export const createPuzzleResponseSchema =
+ puzzleEntitySchema.or(errorResponseSchema);
+
+export type CreatePuzzleRequest = z.infer;
+export type CreatePuzzleResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/submission.schema.ts b/libs/types/src/core/api/schema/submission.schema.ts
new file mode 100644
index 00000000..eca03748
--- /dev/null
+++ b/libs/types/src/core/api/schema/submission.schema.ts
@@ -0,0 +1,20 @@
+import { z } from "zod";
+import { submissionDtoSchema } from "../../submission/schema/submission-dto.schema.js";
+
+// GET /submission/:id response
+export const getSubmissionByIdResponseSchema = submissionDtoSchema;
+export type GetSubmissionByIdResponse = z.infer<
+ typeof getSubmissionByIdResponseSchema
+>;
+
+// POST /submission/game request
+export const submitGameRequestSchema = z.object({
+ gameId: z.string(),
+ code: z.string(),
+ language: z.string(),
+});
+export type SubmitGameRequest = z.infer;
+
+// POST /submission/game response
+export const submitGameResponseSchema = submissionDtoSchema;
+export type SubmitGameResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/submission/submission-api.schema.ts b/libs/types/src/core/api/schema/submission/submission-api.schema.ts
new file mode 100644
index 00000000..58c2f6b3
--- /dev/null
+++ b/libs/types/src/core/api/schema/submission/submission-api.schema.ts
@@ -0,0 +1,108 @@
+import { z } from "zod";
+import { objectIdSchema } from "../../../common/schema/object-id.js";
+import { submissionEntitySchema } from "../../../submission/schema/submission-entity.schema.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+
+/**
+ * POST /submission - Submit code for evaluation
+ * This replaces the generic DTO approach with specific types
+ */
+export const submitCodeRequestSchema = z.object({
+ puzzleId: objectIdSchema,
+ programmingLanguageId: objectIdSchema,
+ code: z.string().min(1, "Code cannot be empty"),
+ userId: objectIdSchema, // Should come from authenticated session
+});
+
+export const submitCodeResponseSchema = z
+ .object({
+ // Specific fields returned on submission
+ submissionId: z.string(),
+ code: z.string(),
+ puzzleId: z.string(),
+ programmingLanguageId: z.string(),
+ userId: z.string(),
+ codeLength: z.number().int().positive(),
+ result: z.object({
+ successRate: z.number().min(0).max(1),
+ passed: z.number().int().nonnegative(),
+ failed: z.number().int().nonnegative(),
+ total: z.number().int().positive(),
+ }),
+ createdAt: z.date().or(z.string()),
+ })
+ .or(errorResponseSchema);
+
+export type SubmitCodeRequest = z.infer;
+export type SubmitCodeResponse = z.infer;
+
+/**
+ * GET /submission/:id - Get submission by ID
+ */
+export const getSubmissionByIdRequestSchema = z.object({
+ id: objectIdSchema,
+});
+
+export const getSubmissionByIdResponseSchema =
+ submissionEntitySchema.or(errorResponseSchema);
+
+export type GetSubmissionByIdRequest = z.infer<
+ typeof getSubmissionByIdRequestSchema
+>;
+export type GetSubmissionByIdResponse = z.infer<
+ typeof getSubmissionByIdResponseSchema
+>;
+
+/**
+ * GET /submission - List user submissions
+ */
+export const listSubmissionsRequestSchema = z.object({
+ userId: objectIdSchema.optional(),
+ puzzleId: objectIdSchema.optional(),
+ page: z.number().int().positive().default(1),
+ pageSize: z.number().int().positive().max(100).default(20),
+});
+
+export const listSubmissionsResponseSchema = z
+ .object({
+ items: z.array(submissionEntitySchema),
+ page: z.number().int().positive(),
+ pageSize: z.number().int().positive(),
+ totalPages: z.number().int().nonnegative(),
+ totalItems: z.number().int().nonnegative(),
+ })
+ .or(errorResponseSchema);
+
+export type ListSubmissionsRequest = z.infer<
+ typeof listSubmissionsRequestSchema
+>;
+export type ListSubmissionsResponse = z.infer<
+ typeof listSubmissionsResponseSchema
+>;
+
+/**
+ * POST /submission/game - Submit an existing submission to a game
+ */
+export const submitToGameRequestSchema = z.object({
+ gameId: objectIdSchema,
+ submissionId: objectIdSchema,
+ userId: objectIdSchema,
+});
+
+export const submitToGameResponseSchema = z
+ .object({
+ success: z.boolean(),
+ message: z.string(),
+ game: z
+ .object({
+ id: objectIdSchema,
+ status: z.enum(["waiting", "in_progress", "completed"]),
+ playerCount: z.number().int().nonnegative(),
+ })
+ .optional(),
+ leaderboardPosition: z.number().int().positive().optional(),
+ })
+ .or(errorResponseSchema);
+
+export type SubmitToGameRequest = z.infer;
+export type SubmitToGameResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/submit-code.schema.ts b/libs/types/src/core/api/schema/submit-code.schema.ts
new file mode 100644
index 00000000..a18354f1
--- /dev/null
+++ b/libs/types/src/core/api/schema/submit-code.schema.ts
@@ -0,0 +1,15 @@
+import { z } from "zod";
+import { submissionEntitySchema } from "../../submission/schema/submission-entity.schema.js";
+import { messageSchema } from "../../common/schema/message.schema.js";
+
+// Submit code request - includes puzzle ID, code, language
+export const submitCodeRequestSchema = z.object({
+ puzzleId: z.string(),
+ code: z.string(),
+ language: z.string(),
+});
+export type SubmitCodeRequest = z.infer;
+
+// Submit code response - returns submission details
+export const submitCodeResponseSchema = submissionEntitySchema;
+export type SubmitCodeResponse = z.infer;
diff --git a/libs/types/src/core/api/schema/user.schema.ts b/libs/types/src/core/api/schema/user.schema.ts
new file mode 100644
index 00000000..8190e625
--- /dev/null
+++ b/libs/types/src/core/api/schema/user.schema.ts
@@ -0,0 +1,40 @@
+import { z } from "zod";
+import { userDtoSchema } from "../../user/schema/user-dto.schema.js";
+import { userActivitySchema } from "../../user/schema/user-activity.schema.js";
+import { paginatedQueryResponseSchema } from "../../common/schema/paginated-query-response.schema.js";
+import { paginatedQuerySchema } from "../../common/schema/paginated-query.schema.js";
+import { puzzleDtoSchema } from "../../puzzle/schema/puzzle-dto.schema.js";
+
+// GET /user/:username response
+export const getUserByUsernameResponseSchema = userDtoSchema;
+export type GetUserByUsernameResponse = z.infer<
+ typeof getUserByUsernameResponseSchema
+>;
+
+// GET /user/:username/puzzle query
+export const getUserPuzzlesQuerySchema = paginatedQuerySchema;
+export type GetUserPuzzlesQuery = z.infer;
+
+// GET /user/:username/puzzle response
+export const getUserPuzzlesResponseSchema = paginatedQueryResponseSchema.extend(
+ {
+ items: z.array(puzzleDtoSchema),
+ },
+);
+export type GetUserPuzzlesResponse = z.infer<
+ typeof getUserPuzzlesResponseSchema
+>;
+
+// GET /user/:username/activity response
+export const getUserActivityResponseSchema = z.array(userActivitySchema);
+export type GetUserActivityResponse = z.infer<
+ typeof getUserActivityResponseSchema
+>;
+
+// GET /user/:username/isAvailable response
+export const usernameIsAvailableResponseSchema = z.object({
+ available: z.boolean(),
+});
+export type UsernameIsAvailableResponse = z.infer<
+ typeof usernameIsAvailableResponseSchema
+>;
diff --git a/libs/types/src/core/api/schema/user/user-api.schema.ts b/libs/types/src/core/api/schema/user/user-api.schema.ts
new file mode 100644
index 00000000..79fe6768
--- /dev/null
+++ b/libs/types/src/core/api/schema/user/user-api.schema.ts
@@ -0,0 +1,13 @@
+import { z } from "zod";
+import { userDtoSchema } from "../../../user/schema/user-dto.schema.js";
+import { errorResponseSchema } from "../../../common/schema/error-response.schema.js";
+
+/**
+ * GET /user/me - Get current user information
+ */
+export const getCurrentUserResponseSchema =
+ userDtoSchema.or(errorResponseSchema);
+
+export type GetCurrentUserResponse = z.infer<
+ typeof getCurrentUserResponseSchema
+>;
diff --git a/libs/types/src/core/comment/schema/comment-dto.schema.ts b/libs/types/src/core/comment/schema/comment-dto.schema.ts
index 887dda4a..c8204259 100644
--- a/libs/types/src/core/comment/schema/comment-dto.schema.ts
+++ b/libs/types/src/core/comment/schema/comment-dto.schema.ts
@@ -4,7 +4,7 @@ import { commentEntitySchema } from "./comment-entity.schema.js";
export const commentDtoSchema = commentEntitySchema.extend({
_id: objectIdSchema,
- comments: z.array(objectIdSchema),
+ comments: z.array(objectIdSchema).optional(),
parentId: objectIdSchema,
});
diff --git a/libs/types/src/core/common/config/backend-urls.ts b/libs/types/src/core/common/config/backend-urls.ts
index 740f8c10..057f618e 100644
--- a/libs/types/src/core/common/config/backend-urls.ts
+++ b/libs/types/src/core/common/config/backend-urls.ts
@@ -43,6 +43,12 @@ export const backendUrls = {
submissionById: (id: string) => `${baseRoute}/submission/${id}`,
SUBMISSION_GAME: `${baseRoute}/submission/game`,
+ // leaderboard routes
+ LEADERBOARD_RECALCULATE: `${baseRoute}/leaderboard/recalculate`,
+ leaderboardByGameMode: (gameMode: string) =>
+ `${baseRoute}/leaderboard/${gameMode}`,
+ leaderboardUserById: (id: string) => `${baseRoute}/leaderboard/user/${id}`,
+
// moderation routes
MODERATION_REVIEW: `${baseRoute}/moderation/review`,
moderationPuzzleApprove: (id: string) =>
@@ -64,4 +70,5 @@ export const backendParams = {
USERNAME: ":username",
ID: ":id",
TYPE: ":type",
+ GAME_MODE: ":gameMode",
} as const;
diff --git a/libs/types/src/core/common/config/error-messages.ts b/libs/types/src/core/common/config/error-messages.ts
new file mode 100644
index 00000000..b871276d
--- /dev/null
+++ b/libs/types/src/core/common/config/error-messages.ts
@@ -0,0 +1,40 @@
+export const ERROR_MESSAGES = {
+ FORM: {
+ VALIDATION_ERRORS: "Form validation errors",
+ REQUIRED_FIELD: "This field is required",
+ },
+ FETCH: {
+ FAILED_TO_LOAD: "Failed to load data",
+ FAILED_TO_FETCH: "Failed to fetch",
+ NETWORK_ERROR: "Network error occurred",
+ },
+ AUTHENTICATION: {
+ INVALID_CREDENTIALS: "Invalid email/username or password",
+ UNAUTHORIZED: "You are not authorized to perform this action",
+ SESSION_EXPIRED: "Your session has expired. Please login again",
+ AUTHENTICATION_REQUIRED: "Authentication required",
+ },
+ MODERATION: {
+ FAILED_TO_FETCH_REVIEW_ITEMS: "Failed to fetch review items",
+ },
+ PUZZLE: {
+ FAILED_TO_DELETE: "Failed to delete the puzzle",
+ FAILED_TO_UPDATE: "Failed to update the puzzle",
+ FAILED_TO_CREATE: "Failed to create the puzzle",
+ NOT_FOUND: "Puzzle not found",
+ },
+ GAME: {
+ NOT_FOUND: "Game not found",
+ USER_NOT_IN_GAME: "User not in this game",
+ ALREADY_FINISHED: "Game has already finished",
+ FAILED_TO_START: "Failed to start game",
+ },
+ SERVER: {
+ INTERNAL_ERROR: "Internal server error",
+ DATABASE_ERROR: "Database error occurred",
+ },
+ GENERIC: {
+ SOMETHING_WENT_WRONG: "Something went wrong",
+ TRY_AGAIN_LATER: "Please try again later",
+ },
+} as const;
diff --git a/libs/types/src/core/common/config/pagination.ts b/libs/types/src/core/common/config/pagination.ts
new file mode 100644
index 00000000..df83bb6b
--- /dev/null
+++ b/libs/types/src/core/common/config/pagination.ts
@@ -0,0 +1,11 @@
+/**
+ * Default pagination configuration values
+ */
+export const PAGINATION_CONFIG = {
+ DEFAULT_PAGE: 1,
+ DEFAULT_LIMIT: 20,
+ DEFAULT_LIMIT_LEADERBOARD: 50,
+ MIN_PAGE: 1,
+ MIN_LIMIT: 1,
+ MAX_LIMIT: 100,
+} as const;
diff --git a/libs/frontend/src/lib/config/test-ids.ts b/libs/types/src/core/common/config/test-ids.ts
similarity index 85%
rename from libs/frontend/src/lib/config/test-ids.ts
rename to libs/types/src/core/common/config/test-ids.ts
index eb8e30db..a9cf167c 100644
--- a/libs/frontend/src/lib/config/test-ids.ts
+++ b/libs/types/src/core/common/config/test-ids.ts
@@ -1,4 +1,4 @@
-import type { ValueOf } from "types";
+import type { ValueOf } from "../types/value-of.js";
export const DATA_TESTID_STRING = "data-testid";
@@ -52,6 +52,7 @@ export const testIds = {
// error component
ERROR_COMPONENT_ANCHOR_HOMEPAGE: "error-component-anchor-homepage",
+ ERROR_COMPONENT_BUTTON_RELOAD: "error-component-button-reload",
// login form
LOGIN_FORM_BUTTON_LOGIN: "login-form-button-login",
@@ -77,6 +78,8 @@ export const testIds = {
MULTIPLAYER_PAGE_BUTTON_JOIN_BY_INVITE:
"multiplayer-page-button-join-by-invite",
MULTIPLAYER_PAGE_BUTTON_COPY_INVITE: "multiplayer-page-button-copy-invite",
+ MULTIPLAYER_PAGE_BUTTON_TOGGLE_INVITE_CODE:
+ "multiplayer-page-button-toggle-invite-code",
// custom game dialog
CUSTOM_GAME_DIALOG_BUTTON_CANCEL: "custom-game-dialog-button-cancel",
@@ -86,6 +89,26 @@ export const testIds = {
JOIN_BY_INVITE_DIALOG_BUTTON_CANCEL: "join-by-invite-dialog-button-cancel",
JOIN_BY_INVITE_DIALOG_BUTTON_JOIN: "join-by-invite-dialog-button-join",
+ // leaderboard page
+ LEADERBOARD_PAGE_BUTTON_MODE_FASTEST: "leaderboard-page-button-mode-fastest",
+ LEADERBOARD_PAGE_BUTTON_MODE_SHORTEST:
+ "leaderboard-page-button-mode-shortest",
+ LEADERBOARD_PAGE_BUTTON_MODE_BACKWARDS:
+ "leaderboard-page-button-mode-backwards",
+ LEADERBOARD_PAGE_BUTTON_MODE_HARDCORE:
+ "leaderboard-page-button-mode-hardcore",
+ LEADERBOARD_PAGE_BUTTON_MODE_DEBUG: "leaderboard-page-button-mode-debug",
+ LEADERBOARD_PAGE_BUTTON_MODE_TYPERACER:
+ "leaderboard-page-button-mode-typeracer",
+ LEADERBOARD_PAGE_BUTTON_MODE_EFFICIENCY:
+ "leaderboard-page-button-mode-efficiency",
+ LEADERBOARD_PAGE_BUTTON_MODE_INCREMENTAL:
+ "leaderboard-page-button-mode-incremental",
+ LEADERBOARD_PAGE_BUTTON_MODE_RANDOM: "leaderboard-page-button-mode-random",
+ LEADERBOARD_PAGE_BUTTON_PREVIOUS_PAGE:
+ "leaderboard-page-button-previous-page",
+ LEADERBOARD_PAGE_BUTTON_NEXT_PAGE: "leaderboard-page-button-next-page",
+
// waiting room chat
WAITING_ROOM_CHAT_BUTTON_SEND: "waiting-room-chat-button-send",
@@ -170,7 +193,7 @@ export const testIds = {
// user hover card component
USER_HOVER_CARD_COMPONENT_ANCHOR_USER_PROFILE:
- "user-hover-card-component-anchor-user-profile"
+ "user-hover-card-component-anchor-user-profile",
} as const;
type DataTestIdMap = typeof testIds;
diff --git a/libs/types/src/core/common/schema/error-response.schema.ts b/libs/types/src/core/common/schema/error-response.schema.ts
index 2b06d0b3..0d0192fc 100644
--- a/libs/types/src/core/common/schema/error-response.schema.ts
+++ b/libs/types/src/core/common/schema/error-response.schema.ts
@@ -3,6 +3,7 @@ import { z } from "zod";
export const errorResponseSchema = z.object({
message: z.string(),
error: z.string(),
+ details: z.array(z.any()).optional(), // Validation error details
});
export type ErrorResponse = z.infer;
diff --git a/libs/types/src/core/game/enum/game-mode-enum.ts b/libs/types/src/core/game/enum/game-mode-enum.ts
index 032e58b4..6f66645b 100644
--- a/libs/types/src/core/game/enum/game-mode-enum.ts
+++ b/libs/types/src/core/game/enum/game-mode-enum.ts
@@ -1,5 +1,20 @@
export const gameModeEnum = {
- FASTEST: "fastest",
- SHORTEST: "shortest",
- RANDOM: "random",
+ // Core modes
+ FASTEST: "fastest", // Complete the task in the shortest time
+ SHORTEST: "shortest", // Write the least amount of code (characters)
+
+ // Challenge modes
+ BACKWARDS: "backwards", // Work from output to input
+ HARDCORE: "hardcore", // One attempt only, no tries
+ DEBUG: "debug", // Fix broken code instead of writing from scratch
+ TYPERACER: "typeracer", // Copy code perfectly, fastest wins
+
+ // Advanced modes
+ EFFICIENCY: "efficiency", // Focus on writing the most efficient code (computationally or memory)
+ INCREMENTAL: "incremental", // Requirements added each minute
+
+ // Special modes
+ RANDOM: "random", // Randomized game mode
} as const;
+
+// dont fucking add random game modes that don't belong
diff --git a/libs/types/src/core/game/enum/game-tournament-enum.ts b/libs/types/src/core/game/enum/game-tournament-enum.ts
new file mode 100644
index 00000000..3368259f
--- /dev/null
+++ b/libs/types/src/core/game/enum/game-tournament-enum.ts
@@ -0,0 +1,5 @@
+export const gameTournamentStyleEnum = {
+ BEST_OF_THREE: "best_of_three", // Multiple rounds, win 2 out of 3
+ ELIMINATION: "elimination", // Tournament bracket style
+ BATTLE_ROYALE: "battle_royale", // Many players, last one standing
+} as const;
diff --git a/libs/types/src/core/game/schema/game-entity.schema.ts b/libs/types/src/core/game/schema/game-entity.schema.ts
index da7298fa..06800356 100644
--- a/libs/types/src/core/game/schema/game-entity.schema.ts
+++ b/libs/types/src/core/game/schema/game-entity.schema.ts
@@ -3,7 +3,7 @@ import { gameOptionsSchema } from "./game-options.schema.js";
import { userDtoSchema } from "../../user/schema/user-dto.schema.js";
import { puzzleDtoSchema } from "../../puzzle/schema/puzzle-dto.schema.js";
import { acceptedDateSchema } from "../../common/schema/accepted-date.js";
-import { submissionDtoSchema } from "../../submission/schema/submission-dto.schema.js";
+import { gameSubmissionSchema } from "./game-submission.schema.js";
import { objectIdSchema } from "../../common/schema/object-id.js";
export const gameEntitySchema = z.object({
@@ -15,7 +15,7 @@ export const gameEntitySchema = z.object({
options: gameOptionsSchema,
createdAt: acceptedDateSchema,
playerSubmissions: z
- .array(objectIdSchema.or(submissionDtoSchema))
+ .array(objectIdSchema.or(gameSubmissionSchema))
.prefault([]),
});
export type GameEntity = z.infer;
diff --git a/libs/types/src/core/game/schema/game-submission.schema.ts b/libs/types/src/core/game/schema/game-submission.schema.ts
new file mode 100644
index 00000000..d6a33ea3
--- /dev/null
+++ b/libs/types/src/core/game/schema/game-submission.schema.ts
@@ -0,0 +1,17 @@
+import { z } from "zod";
+import { submissionDtoSchema } from "../../submission/schema/submission-dto.schema.js";
+
+/**
+ * Extended submission schema specifically for game submissions.
+ * Includes computed fields like codeLength that are added by the backend.
+ */
+export const gameSubmissionSchema = submissionDtoSchema.extend({
+ codeLength: z.number().int().nonnegative().optional(),
+ timeSpent: z.number().nonnegative().optional(), // Time spent in seconds
+});
+
+export type GameSubmission = z.infer;
+
+export function isGameSubmission(data: unknown): data is GameSubmission {
+ return gameSubmissionSchema.safeParse(data).success;
+}
diff --git a/libs/types/src/core/game/schema/mode.schema.ts b/libs/types/src/core/game/schema/mode.schema.ts
index 878f9a01..be67f34e 100644
--- a/libs/types/src/core/game/schema/mode.schema.ts
+++ b/libs/types/src/core/game/schema/mode.schema.ts
@@ -2,7 +2,11 @@ import { z } from "zod";
import { getValues } from "../../../utils/functions/get-values.js";
import { gameModeEnum } from "../enum/game-mode-enum.js";
-export const gameModeSchema = z
- .enum(getValues(gameModeEnum))
- .prefault(gameModeEnum.FASTEST);
+export const gameModes = getValues(gameModeEnum);
+
+export const gameModeSchema = z.enum(gameModes).prefault(gameModeEnum.FASTEST);
export type GameMode = z.infer;
+
+export function isGameMode(data: unknown): data is GameMode {
+ return gameModeSchema.safeParse(data).success;
+}
diff --git a/libs/types/src/core/game/schema/waiting-room-response.schema.ts b/libs/types/src/core/game/schema/waiting-room-response.schema.ts
index cfbaf40f..40fc15a0 100644
--- a/libs/types/src/core/game/schema/waiting-room-response.schema.ts
+++ b/libs/types/src/core/game/schema/waiting-room-response.schema.ts
@@ -52,7 +52,7 @@ const chatMessageResponseSchema = z.object({
event: z.literal(waitingRoomEventEnum.CHAT_MESSAGE),
username: z.string(),
message: z.string(),
- timestamp: acceptedDateSchema,
+ createdAt: acceptedDateSchema,
});
export const waitingRoomResponseSchema = z.discriminatedUnion("event", [
diff --git a/libs/types/src/core/leaderboard/config/leaderboard-config.ts b/libs/types/src/core/leaderboard/config/leaderboard-config.ts
new file mode 100644
index 00000000..f08c0664
--- /dev/null
+++ b/libs/types/src/core/leaderboard/config/leaderboard-config.ts
@@ -0,0 +1,24 @@
+/**
+ * Leaderboard configuration and thresholds
+ */
+export const LEADERBOARD_CONFIG = {
+ RATING_THRESHOLDS: {
+ LEGENDARY: 2000,
+ MASTER: 1800,
+ EXPERT: 1600,
+ ADVANCED: 1400,
+ BEGINNER: 0,
+ },
+ COLORS: {
+ LEGENDARY: "text-purple-600 dark:text-purple-400",
+ MASTER: "text-blue-600 dark:text-blue-400",
+ EXPERT: "text-green-600 dark:text-green-400",
+ ADVANCED: "text-yellow-600 dark:text-yellow-400",
+ BEGINNER: "text-gray-600 dark:text-gray-400",
+ },
+ MEDALS: {
+ FIRST: "🥇",
+ SECOND: "🥈",
+ THIRD: "🥉",
+ },
+} as const;
diff --git a/libs/types/src/core/leaderboard/schema/leaderboard-entry.schema.ts b/libs/types/src/core/leaderboard/schema/leaderboard-entry.schema.ts
new file mode 100644
index 00000000..bbcf21b8
--- /dev/null
+++ b/libs/types/src/core/leaderboard/schema/leaderboard-entry.schema.ts
@@ -0,0 +1,21 @@
+import { z } from "zod";
+import { objectIdSchema } from "../../common/schema/object-id.js";
+import { glickoRatingSchema } from "./user-metrics.schema.js";
+
+/**
+ * Single entry in a leaderboard
+ */
+export const leaderboardEntrySchema = z.object({
+ rank: z.number().int().positive(),
+ userId: objectIdSchema,
+ username: z.string(),
+ rating: z.number(),
+ glicko: glickoRatingSchema,
+ gamesPlayed: z.number().int().nonnegative(),
+ gamesWon: z.number().int().nonnegative(),
+ winRate: z.number().min(0).max(1),
+ bestScore: z.number().nonnegative(),
+ averageScore: z.number().nonnegative(),
+});
+
+export type LeaderboardEntry = z.infer;
diff --git a/libs/types/src/core/leaderboard/schema/user-metrics.schema.ts b/libs/types/src/core/leaderboard/schema/user-metrics.schema.ts
new file mode 100644
index 00000000..959af68e
--- /dev/null
+++ b/libs/types/src/core/leaderboard/schema/user-metrics.schema.ts
@@ -0,0 +1,64 @@
+import { z } from "zod";
+import { objectIdSchema } from "../../common/schema/object-id.js";
+import { gameModeSchema } from "../../game/schema/mode.schema.js";
+import { acceptedDateSchema } from "../../common/schema/accepted-date.js";
+
+/**
+ * Glicko-2 rating components
+ */
+export const glickoRatingSchema = z.object({
+ rating: z.number().default(1500), // Base rating
+ rd: z.number().default(350), // Rating deviation
+ volatility: z.number().default(0.06), // Volatility
+ lastUpdated: acceptedDateSchema.default(() => new Date()),
+});
+
+export type GlickoRating = z.infer;
+
+/**
+ * User metrics per game mode
+ */
+export const gameModeMetricsSchema = z.object({
+ gamesPlayed: z.number().int().nonnegative().default(0),
+ gamesWon: z.number().int().nonnegative().default(0),
+ bestScore: z.number().nonnegative().default(0),
+ averageScore: z.number().nonnegative().default(0),
+ totalScore: z.number().nonnegative().default(0),
+ glickoRating: glickoRatingSchema,
+ rank: z.number().int().positive().optional(), // Position in leaderboard
+ lastGameDate: acceptedDateSchema.optional(),
+});
+
+export type GameModeMetrics = z.infer;
+
+/**
+ * User metrics entity - stores aggregated performance data
+ */
+export const userMetricsEntitySchema = z.object({
+ _id: objectIdSchema.optional(),
+ userId: objectIdSchema,
+
+ // Metrics per game mode
+ fastest: gameModeMetricsSchema.optional(),
+ shortest: gameModeMetricsSchema.optional(),
+ backwards: gameModeMetricsSchema.optional(),
+ hardcore: gameModeMetricsSchema.optional(),
+ debug: gameModeMetricsSchema.optional(),
+ typeracer: gameModeMetricsSchema.optional(),
+ efficiency: gameModeMetricsSchema.optional(),
+ incremental: gameModeMetricsSchema.optional(),
+ random: gameModeMetricsSchema.optional(),
+
+ // Overall stats
+ totalGamesPlayed: z.number().int().nonnegative().default(0),
+ totalGamesWon: z.number().int().nonnegative().default(0),
+
+ // Tracking for incremental updates
+ lastProcessedGameDate: acceptedDateSchema.default(() => new Date(0)), // Epoch
+ lastCalculationDate: acceptedDateSchema.default(() => new Date()),
+
+ createdAt: acceptedDateSchema.default(() => new Date()),
+ updatedAt: acceptedDateSchema.default(() => new Date()),
+});
+
+export type UserMetricsEntity = z.infer;
diff --git a/libs/types/src/core/programming-language/schema/create-programming-language.schema.ts b/libs/types/src/core/programming-language/schema/create-programming-language.schema.ts
index 236c8bea..bf723e49 100644
--- a/libs/types/src/core/programming-language/schema/create-programming-language.schema.ts
+++ b/libs/types/src/core/programming-language/schema/create-programming-language.schema.ts
@@ -1,8 +1,5 @@
import { z } from "zod";
-/**
- * Schema for creating a new programming language
- */
export const createProgrammingLanguageSchema = z.object({
language: z.string().min(1, "Language name is required"),
version: z.string().min(1, "Language version is required"),
diff --git a/libs/types/src/core/puzzle/schema/puzzle-dto.schema.ts b/libs/types/src/core/puzzle/schema/puzzle-dto.schema.ts
index 340178d7..8694b612 100644
--- a/libs/types/src/core/puzzle/schema/puzzle-dto.schema.ts
+++ b/libs/types/src/core/puzzle/schema/puzzle-dto.schema.ts
@@ -18,6 +18,5 @@ export const puzzleDtoSchema = basePuzzleDtoSchema.extend({
export type PuzzleDto = z.infer;
export function isPuzzleDto(data: unknown): data is PuzzleDto {
- console.log({ result: puzzleDtoSchema.safeParse(data) });
return puzzleDtoSchema.safeParse(data).success;
}
diff --git a/libs/types/src/core/submission/schema/submission-entity.schema.ts b/libs/types/src/core/submission/schema/submission-entity.schema.ts
index ef9f58b1..47147ebb 100644
--- a/libs/types/src/core/submission/schema/submission-entity.schema.ts
+++ b/libs/types/src/core/submission/schema/submission-entity.schema.ts
@@ -8,11 +8,13 @@ import { puzzleResultInformationSchema } from "../../piston/schema/puzzle-result
export const submissionEntitySchema = z.object({
code: z.string().optional(),
- codeLength: z.number().optional(),
+ // codelenght shouldn't be added here, since it should be derived from the code itself
+ // codelength should also be returned by a more specific dto/schema instead of this one, this one is too generic
programmingLanguage: objectIdSchema.or(programmingLanguageDtoSchema),
createdAt: acceptedDateSchema.prefault(() => new Date()),
puzzle: objectIdSchema.or(puzzleDtoSchema),
result: puzzleResultInformationSchema,
user: objectIdSchema.or(userDtoSchema),
});
+
export type SubmissionEntity = z.infer;
diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts
index 086dca1d..8d180873 100644
--- a/libs/types/src/index.ts
+++ b/libs/types/src/index.ts
@@ -36,7 +36,10 @@ export * from "./core/common/config/backend-urls.js";
export * from "./core/common/config/cookie.js";
export * from "./core/common/config/environment.js";
export * from "./core/common/config/default-values-query-params.js";
+export * from "./core/common/config/error-messages.js";
export * from "./core/common/config/frontend-urls.js";
+export * from "./core/common/config/pagination.js";
+export * from "./core/common/config/test-ids.js";
export * from "./core/common/config/web-socket-urls.js";
export * from "./core/common/enum/websocket-close-codes.js";
export * from "./core/common/enum/http-response-codes.js";
@@ -60,6 +63,7 @@ export * from "./core/game/enum/game-visibility-enum.js";
export * from "./core/game/schema/game-dto.schema.js";
export * from "./core/game/schema/game-entity.schema.js";
export * from "./core/game/schema/game-user-info.schema.js";
+export * from "./core/game/schema/game-submission.schema.js";
export * from "./core/game/schema/waiting-room-request.schema.js";
export * from "./core/game/schema/waiting-room-response.schema.js";
export * from "./core/game/schema/game-request.schema.js";
@@ -68,6 +72,11 @@ export * from "./core/game/schema/mode.schema.js";
export * from "./core/game/schema/game-options.schema.js";
export * from "./core/game/schema/visibility.schema.js";
+// leaderboard
+export * from "./core/leaderboard/config/leaderboard-config.js";
+export * from "./core/leaderboard/schema/user-metrics.schema.js";
+export * from "./core/leaderboard/schema/leaderboard-entry.schema.js";
+
// moderation
export * from "./core/moderation/config/report-config.js";
export * from "./core/moderation/config/ban-config.js";
@@ -143,6 +152,29 @@ export * from "./core/user/schema/user-entity.schema.js";
export * from "./core/user/schema/user-vote-entity.schema.js";
export * from "./core/user/schema/user-profile.schema.js";
+// api - request/response types for all endpoints
+export * from "./core/api/schema/execute-code.schema.js";
+export * from "./core/api/schema/submit-code.schema.js";
+export * from "./core/api/schema/programming-language.schema.js";
+export * from "./core/api/schema/user.schema.js";
+export * from "./core/api/schema/comment.schema.js";
+export * from "./core/api/schema/submission.schema.js";
+export * from "./core/api/schema/puzzle.schema.js";
+export * from "./core/api/schema/account.schema.js";
+export * from "./core/api/schema/moderation.schema.js";
+
+// New specific API endpoint types (v2 - more specific, less generic)
+export * as AuthAPI from "./core/api/schema/auth/login.schema.js";
+export * as RegisterAPI from "./core/api/schema/auth/register.schema.js";
+export * as LogoutAPI from "./core/api/schema/auth/logout.schema.js";
+export * as PuzzleAPI from "./core/api/schema/puzzle/puzzle-api.schema.js";
+export * as UserAPI from "./core/api/schema/user/user-api.schema.js";
+export * as ProgrammingLanguageAPI from "./core/api/schema/programming-language/programming-language-api.schema.js";
+export * as SubmissionAPI from "./core/api/schema/submission/submission-api.schema.js";
+export * as ExecuteAPI from "./core/api/schema/execute/execute-api.schema.js";
+export * as GameAPI from "./core/api/schema/game/game-api.schema.js";
+export * as LeaderboardAPI from "./core/api/schema/leaderboard/leaderboard-api.schema.js";
+
// utils - constants
export * from "./utils/constants/http-methods.js";
diff --git a/libs/types/src/utils/functions/get-user-id-from-user.ts b/libs/types/src/utils/functions/get-user-id-from-user.ts
index 57ed52f3..5b43cc43 100644
--- a/libs/types/src/utils/functions/get-user-id-from-user.ts
+++ b/libs/types/src/utils/functions/get-user-id-from-user.ts
@@ -9,7 +9,7 @@ export function getUserIdFromUser(user: unknown): string | null {
}
if (typeof user === "object" && user !== null && "_id" in user) {
- const id = (user as any)._id;
+ const id = user._id;
return id ? String(id) : null;
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3d535280..85153c9f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
'@fastify/websocket':
specifier: ^10.0.1
version: 10.0.1
+ '@types/node-cron':
+ specifier: ^3.0.11
+ version: 3.0.11
bcryptjs:
specifier: ^3.0.2
version: 3.0.2
@@ -50,6 +53,9 @@ importers:
mongoose:
specifier: ^8.5.1
version: 8.19.2
+ node-cron:
+ specifier: ^4.2.1
+ version: 4.2.1
zod:
specifier: ^4.1.12
version: 4.1.12
@@ -106,6 +112,22 @@ importers:
specifier: ^3.0.8
version: 3.2.4(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)
+ libs/e2e:
+ dependencies:
+ types:
+ specifier: workspace:*
+ version: link:../types
+ devDependencies:
+ '@playwright/test':
+ specifier: ^1.48.2
+ version: 1.56.1
+ '@types/node':
+ specifier: ^22.10.2
+ version: 22.18.13
+ typescript:
+ specifier: ^5.7.2
+ version: 5.9.3
+
libs/frontend:
dependencies:
'@codemirror/autocomplete':
@@ -162,9 +184,6 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
- codemirror:
- specifier: ^6.0.1
- version: 6.0.2
codemirror-lang-elixir:
specifier: ^4.0.0
version: 4.0.0
@@ -193,9 +212,12 @@ importers:
'@eslint/js':
specifier: ^9.7.0
version: 9.38.0
+ '@internationalized/date':
+ specifier: ^3.10.0
+ version: 3.10.0
'@lucide/svelte':
- specifier: ^0.548.0
- version: 0.548.0(svelte@5.41.4)
+ specifier: ^0.552.0
+ version: 0.552.0(svelte@5.41.4)
'@sveltejs/adapter-node':
specifier: 5.2.12
version: 5.2.12(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))
@@ -227,8 +249,8 @@ importers:
specifier: ^8.22.0
version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)
bits-ui:
- specifier: ^1.8.0
- version: 1.8.0(svelte@5.41.4)
+ specifier: ^2.14.1
+ version: 2.14.1(@internationalized/date@3.10.0)(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)
eslint:
specifier: ^9.10.0
version: 9.38.0(jiti@2.6.1)
@@ -1136,8 +1158,8 @@ packages:
'@lezer/rust@1.0.2':
resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==}
- '@lucide/svelte@0.548.0':
- resolution: {integrity: sha512-Iwh5GXK8+tE1lBjYoBPfOhBiWv6/K/XinZ/Bjx/2Qys86ufZ/CbjHKacYyZeGnyWQSsjcr7P3xavkmCMhv/1RA==}
+ '@lucide/svelte@0.552.0':
+ resolution: {integrity: sha512-8wQF1YUKgaXiFidPdHM5NKESArKHLrgf8A1EAOjvqRmdVlL2KFsPxTchez/lMUOJKxXeDgrRKfXQuaN1O5KlhQ==}
peerDependencies:
svelte: ^5
@@ -1166,6 +1188,11 @@ packages:
'@pinojs/redact@0.4.0':
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
+ '@playwright/test@1.56.1':
+ resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
@@ -1574,6 +1601,12 @@ packages:
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+ '@types/node-cron@3.0.11':
+ resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==}
+
+ '@types/node@22.18.13':
+ resolution: {integrity: sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==}
+
'@types/node@24.9.1':
resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==}
@@ -1805,11 +1838,12 @@ packages:
birpc@0.2.14:
resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==}
- bits-ui@1.8.0:
- resolution: {integrity: sha512-CXD6Orp7l8QevNDcRPLXc/b8iMVgxDWT2LyTwsdLzJKh9CxesOmPuNePSPqAxKoT59FIdU4aFPS1k7eBdbaCxg==}
- engines: {node: '>=18', pnpm: '>=8.7.0'}
+ bits-ui@2.14.1:
+ resolution: {integrity: sha512-FkQTBDF+BLh5fgwioi04JJD8kpsQ+pVjPwzxUYYd39pYR0uKAyMLmoKo3EAWJIszV7fjTv1ZiNzxTkpGutJ32w==}
+ engines: {node: '>=20'}
peerDependencies:
- svelte: ^5.11.0
+ '@internationalized/date': ^3.8.1
+ svelte: ^5.33.0
blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
@@ -1884,9 +1918,6 @@ packages:
codemirror-lang-prolog@0.1.0:
resolution: {integrity: sha512-l8UvvCy3ub9kHbREFPG44xhHNG/AuCwkQEbLANfppHi1qZEWdr59ChSo4ZVu5XmC4PrHH3aMUHF+E2KS/V+LpA==}
- codemirror@6.0.2:
- resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
-
'codin-cod@file:':
resolution: {directory: '', type: directory}
@@ -1933,9 +1964,6 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
- css.escape@1.5.1:
- resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
-
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -1967,6 +1995,10 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -2229,6 +2261,11 @@ packages:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2511,6 +2548,10 @@ packages:
loupe@3.2.1:
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -2633,6 +2674,10 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ node-cron@4.2.1:
+ resolution: {integrity: sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==}
+ engines: {node: '>=6.0.0'}
+
normalize-url@8.1.0:
resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==}
engines: {node: '>=14.16'}
@@ -2723,6 +2768,16 @@ packages:
typescript:
optional: true
+ playwright-core@1.56.1:
+ resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.56.1:
+ resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
postcss-load-config@3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
@@ -2926,6 +2981,15 @@ packages:
peerDependencies:
svelte: ^5.7.0
+ runed@0.35.1:
+ resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==}
+ peerDependencies:
+ '@sveltejs/kit': ^2.21.0
+ svelte: ^5.7.0
+ peerDependenciesMeta:
+ '@sveltejs/kit':
+ optional: true
+
sade@1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
@@ -3070,6 +3134,12 @@ packages:
peerDependencies:
svelte: ^5.0.0
+ svelte-toolbelt@0.10.6:
+ resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==}
+ engines: {node: '>=18', pnpm: '>=8.7.0'}
+ peerDependencies:
+ svelte: ^5.30.2
+
svelte-toolbelt@0.4.6:
resolution: {integrity: sha512-k8OUvXBUifHZcAlWeY/HLg/4J0v5m2iOfOhn8fDmjt4AP8ZluaDh9eBFus9lFiLX6O5l6vKqI1dKL5wy7090NQ==}
engines: {node: '>=18', pnpm: '>=8.7.0'}
@@ -3227,6 +3297,9 @@ packages:
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -4152,7 +4225,7 @@ snapshots:
'@lezer/highlight': 1.2.2
'@lezer/lr': 1.4.2
- '@lucide/svelte@0.548.0(svelte@5.41.4)':
+ '@lucide/svelte@0.552.0(svelte@5.41.4)':
dependencies:
svelte: 5.41.4
@@ -4178,6 +4251,10 @@ snapshots:
'@pinojs/redact@0.4.0': {}
+ '@playwright/test@1.56.1':
+ dependencies:
+ playwright: 1.56.1
+
'@polka/url@1.0.0-next.29': {}
'@poppinss/colors@4.1.5':
@@ -4534,6 +4611,12 @@ snapshots:
'@types/ms@2.1.0': {}
+ '@types/node-cron@3.0.11': {}
+
+ '@types/node@22.18.13':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/node@24.9.1':
dependencies:
undici-types: 7.16.0
@@ -4803,17 +4886,18 @@ snapshots:
birpc@0.2.14: {}
- bits-ui@1.8.0(svelte@5.41.4):
+ bits-ui@2.14.1(@internationalized/date@3.10.0)(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4):
dependencies:
'@floating-ui/core': 1.7.3
'@floating-ui/dom': 1.7.4
'@internationalized/date': 3.10.0
- css.escape: 1.5.1
esm-env: 1.2.2
- runed: 0.23.4(svelte@5.41.4)
+ runed: 0.35.1(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)
svelte: 5.41.4
- svelte-toolbelt: 0.7.1(svelte@5.41.4)
+ svelte-toolbelt: 0.10.6(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)
tabbable: 6.3.0
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
blake3-wasm@2.1.5: {}
@@ -4889,16 +4973,6 @@ snapshots:
'@lezer/highlight': 1.2.2
'@lezer/lr': 1.4.2
- codemirror@6.0.2:
- dependencies:
- '@codemirror/autocomplete': 6.19.1
- '@codemirror/commands': 6.10.0
- '@codemirror/language': 6.11.3
- '@codemirror/lint': 6.9.1
- '@codemirror/search': 6.5.11
- '@codemirror/state': 6.5.2
- '@codemirror/view': 6.38.6
-
'codin-cod@file:': {}
color-convert@2.0.1:
@@ -4937,8 +5011,6 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
- css.escape@1.5.1: {}
-
cssesc@3.0.0: {}
dayjs@1.11.18: {}
@@ -4955,6 +5027,8 @@ snapshots:
defu@6.1.4: {}
+ dequal@2.0.3: {}
+
detect-libc@2.1.2: {}
devalue@5.4.2: {}
@@ -5319,6 +5393,9 @@ snapshots:
forwarded@0.2.0: {}
+ fsevents@2.3.2:
+ optional: true
+
fsevents@2.3.3:
optional: true
@@ -5560,6 +5637,8 @@ snapshots:
loupe@3.2.1: {}
+ lz-string@1.5.0: {}
+
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -5693,6 +5772,8 @@ snapshots:
natural-compare@1.4.0: {}
+ node-cron@4.2.1: {}
+
normalize-url@8.1.0:
optional: true
@@ -5789,6 +5870,14 @@ snapshots:
optionalDependencies:
typescript: 5.9.3
+ playwright-core@1.56.1: {}
+
+ playwright@1.56.1:
+ dependencies:
+ playwright-core: 1.56.1
+ optionalDependencies:
+ fsevents: 2.3.2
+
postcss-load-config@3.1.4(postcss@8.5.6):
dependencies:
lilconfig: 2.1.0
@@ -5936,6 +6025,15 @@ snapshots:
esm-env: 1.2.2
svelte: 5.41.4
+ runed@0.35.1(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4):
+ dependencies:
+ dequal: 2.0.3
+ esm-env: 1.2.2
+ lz-string: 1.5.0
+ svelte: 5.41.4
+ optionalDependencies:
+ '@sveltejs/kit': 2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1))
+
sade@1.8.1:
dependencies:
mri: 1.2.0
@@ -6094,6 +6192,15 @@ snapshots:
runed: 0.28.0(svelte@5.41.4)
svelte: 5.41.4
+ svelte-toolbelt@0.10.6(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4):
+ dependencies:
+ clsx: 2.1.1
+ runed: 0.35.1(@sveltejs/kit@2.47.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.41.4)
+ style-to-object: 1.0.12
+ svelte: 5.41.4
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
+
svelte-toolbelt@0.4.6(svelte@5.41.4):
dependencies:
clsx: 2.1.1
@@ -6257,6 +6364,8 @@ snapshots:
ufo@1.6.1: {}
+ undici-types@6.21.0: {}
+
undici-types@7.16.0: {}
undici@7.14.0: {}