From 366fe93405b74da67ade9574791e7dd4c63fec50 Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Mon, 26 Feb 2024 13:52:55 -0500 Subject: [PATCH] Initial public scoreboard --- web/docker/dev-postgress/docker-compose.yml | 8 +- .../admin/contests/import/+page.server.ts | 2 +- .../routes/admin/scoreboard/+page.server.ts | 2 +- .../public/scoreboard/+layout.server.ts | 7 ++ .../routes/public/scoreboard/+layout.svelte | 44 +++++++ web/src/routes/public/scoreboard/+page.svelte | 5 + .../scoreboard/[contestId]/+page.server.ts | 113 ++++++++++++++++++ .../scoreboard/[contestId]/+page.svelte | 8 ++ web/src/routes/public/scoreboard/stores.ts | 3 + 9 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 web/src/routes/public/scoreboard/+layout.server.ts create mode 100644 web/src/routes/public/scoreboard/+layout.svelte create mode 100644 web/src/routes/public/scoreboard/+page.svelte create mode 100644 web/src/routes/public/scoreboard/[contestId]/+page.server.ts create mode 100644 web/src/routes/public/scoreboard/[contestId]/+page.svelte create mode 100644 web/src/routes/public/scoreboard/stores.ts diff --git a/web/docker/dev-postgress/docker-compose.yml b/web/docker/dev-postgress/docker-compose.yml index 9da0ad7..efec47c 100644 --- a/web/docker/dev-postgress/docker-compose.yml +++ b/web/docker/dev-postgress/docker-compose.yml @@ -1,18 +1,18 @@ version: '3' services: db: - image: "postgres:latest" + image: 'postgres:latest' container_name: 'bwcontest-postgres' restart: unless-stopped environment: POSTGRES_DB: 'bwcontest' POSTGRES_USER: 'bwcontest' - POSTGRES_PASSWORD: 'password' + POSTGRES_PASSWORD: 'pass123' TZ: America/New_York ports: - - '5432:5432' + - '5433:5432' volumes: - bwcontest-postgres-data:/var/lib/postgresql/data volumes: - bwcontest-postgres-data: \ No newline at end of file + bwcontest-postgres-data: diff --git a/web/src/routes/admin/contests/import/+page.server.ts b/web/src/routes/admin/contests/import/+page.server.ts index 789256f..986ea14 100644 --- a/web/src/routes/admin/contests/import/+page.server.ts +++ b/web/src/routes/admin/contests/import/+page.server.ts @@ -123,7 +123,7 @@ export const actions = { } } }) - ) + ) : [] } } diff --git a/web/src/routes/admin/scoreboard/+page.server.ts b/web/src/routes/admin/scoreboard/+page.server.ts index 006f36d..e87f854 100644 --- a/web/src/routes/admin/scoreboard/+page.server.ts +++ b/web/src/routes/admin/scoreboard/+page.server.ts @@ -64,7 +64,7 @@ export const load = (async () => { return ( submission.problemId === problem.id && submission.state === 'Correct' ); - }) + }) ? 'correct' : 'incorrect' : null, diff --git a/web/src/routes/public/scoreboard/+layout.server.ts b/web/src/routes/public/scoreboard/+layout.server.ts new file mode 100644 index 0000000..fe14bcc --- /dev/null +++ b/web/src/routes/public/scoreboard/+layout.server.ts @@ -0,0 +1,7 @@ +import { db } from '$lib/server/prisma'; +import type { LayoutServerLoad } from './$types'; + +export const load = (async () => { + const contests = await db.contest.findMany({ select: { id: true, name: true } }); + return { contests }; +}) satisfies LayoutServerLoad; diff --git a/web/src/routes/public/scoreboard/+layout.svelte b/web/src/routes/public/scoreboard/+layout.svelte new file mode 100644 index 0000000..3d3260a --- /dev/null +++ b/web/src/routes/public/scoreboard/+layout.svelte @@ -0,0 +1,44 @@ + + +
+
+ +
+ + +
+
+
+ diff --git a/web/src/routes/public/scoreboard/+page.svelte b/web/src/routes/public/scoreboard/+page.svelte new file mode 100644 index 0000000..2e5aa58 --- /dev/null +++ b/web/src/routes/public/scoreboard/+page.svelte @@ -0,0 +1,5 @@ + diff --git a/web/src/routes/public/scoreboard/[contestId]/+page.server.ts b/web/src/routes/public/scoreboard/[contestId]/+page.server.ts new file mode 100644 index 0000000..85a4542 --- /dev/null +++ b/web/src/routes/public/scoreboard/[contestId]/+page.server.ts @@ -0,0 +1,113 @@ +import { db } from '$lib/server/prisma'; +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load = (async ({ params }) => { + const contestId = parseInt(params.contestId); + if (isNaN(contestId)) { + throw redirect(302, '/public/scoreboard'); + } + const timestamp = new Date(); + const contest = await db.contest.findUnique({ + where: { id: contestId }, + include: { problems: true, teams: { include: { submissions: true } } } + }); + if (contest === null) { + throw redirect(302, '/public/scoreboard'); + } + const data = { + timestamp: timestamp, + selected: { + name: contest.name, + problems: contest.problems.map((problem) => { + return { id: problem.id, friendlyName: problem.friendlyName }; + }), + teams: contest.teams + .map((team) => { + return { + name: team.name, + solves: team.submissions.filter((submission) => { + return submission.contestId === contest.id && submission.state === 'Correct'; + }).length, + time: (() => { + const correctSubmissions = team.submissions.filter((submission) => { + return submission.contestId === contest.id && submission.state === 'Correct'; + }); + const penaltyTime = + team.submissions.filter((submission) => { + return ( + submission.contestId === contest.id && + submission.state === 'Incorrect' && + correctSubmissions.find((correct) => { + return correct.problemId === submission.problemId; + }) + ); + }).length * 10; + let time = penaltyTime; + correctSubmissions.forEach((correctSubmission) => { + const gradedAt = correctSubmission.gradedAt!.valueOf(); + const min = (gradedAt - contest.startTime!.valueOf()) / 60000; + time += min; + }); + return time; + })(), + problems: contest.problems.map((problem) => { + return { + id: problem.id, + attempts: team.submissions.filter((submission) => { + return ( + submission.contestId === contest.id && + submission.problemId === problem.id && + (submission.state === 'Correct' || submission.state === 'Incorrect') + ); + }).length, + graphic: team.submissions.find((submission) => { + return ( + submission.contestId === contest.id && + submission.problemId === problem.id && + (submission.state === 'Correct' || submission.state === 'Incorrect') + ); + }) + ? team.submissions.find((submission) => { + return submission.problemId === problem.id && submission.state === 'Correct'; + }) + ? 'correct' + : 'incorrect' + : null, + min: (() => { + const correctSubmission = team.submissions.find((submission) => { + return ( + submission.contestId === contest.id && + submission.problemId === problem.id && + submission.state === 'Correct' + ); + }); + if (correctSubmission) { + const gradedAt = correctSubmission.gradedAt!.valueOf(); + return (gradedAt - contest.startTime!.valueOf()) / 60000; + } + return undefined; + })() + }; + }) + }; + }) + .sort((a, b) => { + if (a.solves > b.solves) { + return -1; + } else if (a.solves < b.solves) { + return 1; + } else { + if (a.time < b.time) { + return -1; + } else if (a.time > b.time) { + return 1; + } else { + return 0; + } + } + }) + } + }; + return data; +}) satisfies PageServerLoad; diff --git a/web/src/routes/public/scoreboard/[contestId]/+page.svelte b/web/src/routes/public/scoreboard/[contestId]/+page.svelte new file mode 100644 index 0000000..3240f76 --- /dev/null +++ b/web/src/routes/public/scoreboard/[contestId]/+page.svelte @@ -0,0 +1,8 @@ + diff --git a/web/src/routes/public/scoreboard/stores.ts b/web/src/routes/public/scoreboard/stores.ts new file mode 100644 index 0000000..b5278b8 --- /dev/null +++ b/web/src/routes/public/scoreboard/stores.ts @@ -0,0 +1,3 @@ +import { writable } from 'svelte/store'; + +export const contestId = writable(null);