diff --git a/web/prisma/ERD.svg b/web/prisma/ERD.svg index 060ed3f..4196b62 100644 --- a/web/prisma/ERD.svg +++ b/web/prisma/ERD.svg @@ -1 +1 @@ -SubmissionStateQueuedQueuedInReviewInReviewCorrectCorrectIncorrectIncorrectUserIntid🗝️StringusernameStringpasswordSessionStringtoken🗝️DateTimecreatedAtSubmissionIntid🗝️DateTimecreatedAtDateTimegradedAtSubmissionStatestateStringactualOutputStringcommitHashStringdiffStringmessageProblemIntid🗝️StringfriendlyNameStringpascalNameStringsampleInputStringsampleOutputStringrealInputStringrealOutputTeamIntid🗝️StringnameStringpasswordActiveTeamIntid🗝️StringsessionTokenDateTimesessionCreatedAtContestIntid🗝️Stringnamesessionsuserenum:stateteamproblemcontestsubmissionscontestsSubmissioncontestsactiveTeamteamcontestteamsproblemsactiveTeamssubmissions \ No newline at end of file +SubmissionStateQueuedQueuedInReviewInReviewCorrectCorrectIncorrectIncorrectUserIntid🗝️StringusernameStringpasswordSessionStringtoken🗝️DateTimecreatedAtSubmissionIntid🗝️DateTimecreatedAtDateTimegradedAtSubmissionStatestateStringactualOutputStringcommitHashStringdiffStringmessageProblemIntid🗝️StringfriendlyNameStringpascalNameStringsampleInputStringsampleOutputStringrealInputStringrealOutputTeamIntid🗝️StringnameStringpasswordActiveTeamIntid🗝️StringsessionTokenDateTimesessionCreatedAtContestIntid🗝️StringnameDateTimestartTimesessionsuserenum:stateteamproblemcontestsubmissionscontestssubmissionscontestsactiveTeamteamcontestteamsproblemsactiveTeamssubmissions \ No newline at end of file diff --git a/web/prisma/schema.prisma b/web/prisma/schema.prisma index bfe14dd..67ea5bb 100644 --- a/web/prisma/schema.prisma +++ b/web/prisma/schema.prisma @@ -62,12 +62,12 @@ model Problem { } model Team { - id Int @id @default(autoincrement()) - name String @unique - Submission Submission[] - contests Contest[] @relation("TeamContestRelation") - password String - activeTeam ActiveTeam? + id Int @id @default(autoincrement()) + name String @unique + submissions Submission[] + contests Contest[] @relation("TeamContestRelation") + password String + activeTeam ActiveTeam? } model ActiveTeam { @@ -87,4 +87,5 @@ model Contest { problems Problem[] @relation("ProblemContestRelation") activeTeams ActiveTeam[] submissions Submission[] + startTime DateTime? } diff --git a/web/src/routes/admin/+layout.svelte b/web/src/routes/admin/+layout.svelte index d652b4b..4dec39b 100644 --- a/web/src/routes/admin/+layout.svelte +++ b/web/src/routes/admin/+layout.svelte @@ -4,7 +4,7 @@
  • Reviews
  • Submissions
  • Problems
  • -
  • Scoreboard
  • +
  • Scoreboards
  • Teams
  • Contests
  • Logout
  • diff --git a/web/src/routes/admin/contests/+page.server.ts b/web/src/routes/admin/contests/+page.server.ts index 9270b13..fe18bbf 100644 --- a/web/src/routes/admin/contests/+page.server.ts +++ b/web/src/routes/admin/contests/+page.server.ts @@ -2,10 +2,10 @@ import { db } from '$lib/server/prisma'; import type { PageServerLoad } from './$types'; export const load = (async () => { - const contests = await db.contest.findMany(); + const contests = await db.contest.findMany({ include: { activeTeams: true } }); return { contests: contests.map((contest) => { - return { id: contest.id, name: contest.name }; + return { id: contest.id, name: contest.name, activeTeams: contest.activeTeams.length }; }) }; }) satisfies PageServerLoad; diff --git a/web/src/routes/admin/contests/+page.svelte b/web/src/routes/admin/contests/+page.svelte index cb51504..8896a44 100644 --- a/web/src/routes/admin/contests/+page.svelte +++ b/web/src/routes/admin/contests/+page.svelte @@ -20,7 +20,9 @@ {#each data.contests as contest} {contest.name}{contest.name} {/each} diff --git a/web/src/routes/admin/contests/[contestId]/+page.server.ts b/web/src/routes/admin/contests/[contestId]/+page.server.ts index 26e4f85..329635e 100644 --- a/web/src/routes/admin/contests/[contestId]/+page.server.ts +++ b/web/src/routes/admin/contests/[contestId]/+page.server.ts @@ -68,6 +68,8 @@ export const actions = { await db.activeTeam.create({ data: { teamId: team.id, contestId: contest.id } }); }); + await db.contest.update({ where: { id: contestId }, data: { startTime: new Date() } }); + return { success: true }; }, stop: async ({ params }) => { diff --git a/web/src/routes/admin/diff/[submissionId]/+page.server.ts b/web/src/routes/admin/diff/[submissionId]/+page.server.ts index fda6c42..f514f08 100644 --- a/web/src/routes/admin/diff/[submissionId]/+page.server.ts +++ b/web/src/routes/admin/diff/[submissionId]/+page.server.ts @@ -1,5 +1,4 @@ import type { Actions, PageServerLoad } from './$types'; -import * as Diff from 'diff'; import { error, redirect } from '@sveltejs/kit'; import { db } from '$lib/server/prisma'; import { SubmissionState } from '@prisma/client'; @@ -17,17 +16,8 @@ export const load = (async ({ params }) => { if (!problem) { throw error(500, 'Invalid problem'); } - let diff: string | undefined; - if (submission.actualOutput) { - diff = Diff.createTwoFilesPatch( - 'expected', - 'actual', - problem.realOutput, - submission.actualOutput - ); - } - return { diff: diff }; + return { diff: submission.diff, submissionId: submission.id }; }) satisfies PageServerLoad; export const actions = { diff --git a/web/src/routes/admin/diff/[submissionId]/+page.svelte b/web/src/routes/admin/diff/[submissionId]/+page.svelte index 1049cb1..74c8f0d 100644 --- a/web/src/routes/admin/diff/[submissionId]/+page.svelte +++ b/web/src/routes/admin/diff/[submissionId]/+page.svelte @@ -28,15 +28,17 @@ } onMount(() => { - const diff2htmlUi = new Diff2HtmlUI(document.getElementById('diff')!, data.diff, { - drawFileList: false, - matching: 'lines', - diffStyle: 'char', - outputFormat: 'side-by-side', - highlight: false, - fileContentToggle: false - }); - diff2htmlUi.draw(); + if (data.diff) { + const diff2htmlUi = new Diff2HtmlUI(document.getElementById('diff')!, data.diff, { + drawFileList: false, + matching: 'lines', + diffStyle: 'char', + outputFormat: 'side-by-side', + highlight: false, + fileContentToggle: false + }); + diff2htmlUi.draw(); + } incorrectBtn.addEventListener('change', () => { submitBtn.disabled = false; @@ -61,7 +63,11 @@
    Success!
    {/if} -All Reviews +
    + All Reviews + Go to Submission +
    +
    diff --git a/web/src/routes/admin/reviews/+page.server.ts b/web/src/routes/admin/reviews/+page.server.ts index 9487e6d..a328a89 100644 --- a/web/src/routes/admin/reviews/+page.server.ts +++ b/web/src/routes/admin/reviews/+page.server.ts @@ -1,6 +1,6 @@ import { db } from '$lib/server/prisma'; import { SubmissionState } from '@prisma/client'; -import type { Actions, PageServerLoad } from './$types'; +import type {PageServerLoad } from './$types'; export const load = (async () => { const submissions = await db.submission.findMany({ where: { state: SubmissionState.InReview } }); @@ -19,44 +19,3 @@ export const load = (async () => { }; }) satisfies PageServerLoad; -// export const actions = { -// submission: async ({ request }) => { -// const data = await request.formData(); -// const teamId = data.get('teamId'); -// const problemId = data.get('problemId'); -// const actual = data.get('actual'); -// if (!teamId || !problemId || !actual) { -// return { success: false }; -// } -// const problemIdInt = parseInt(problemId.toString()); -// const teamIdInt = parseInt(teamId.toString()); -// if (isNaN(problemIdInt) || isNaN(teamIdInt)) { -// return { success: false }; -// } -// const problem = await db.problem.findUnique({ where: { id: problemIdInt } }); -// if (!problem) { -// return { success: false }; -// } -// if (problem.realOutput === actual.toString()) { -// await db.submission.create({ -// data: { -// state: SubmissionState.Correct, -// actualOutput: actual.toString(), -// teamId: teamIdInt, -// problemId: problemIdInt, -// gradedAt: new Date() -// } -// }); -// return { success: true }; -// } -// await db.submission.create({ -// data: { -// state: SubmissionState.InReview, -// actualOutput: actual.toString(), -// teamId: teamIdInt, -// problemId: problemIdInt -// } -// }); -// return { success: true }; -// } -// } satisfies Actions; diff --git a/web/src/routes/admin/reviews/+page.svelte b/web/src/routes/admin/reviews/+page.svelte index bfea196..67ed3d3 100644 --- a/web/src/routes/admin/reviews/+page.svelte +++ b/web/src/routes/admin/reviews/+page.svelte @@ -20,66 +20,3 @@ > {/each} - - diff --git a/web/src/routes/admin/scoreboard/+page.server.ts b/web/src/routes/admin/scoreboard/+page.server.ts index f6a555c..c4fd0bb 100644 --- a/web/src/routes/admin/scoreboard/+page.server.ts +++ b/web/src/routes/admin/scoreboard/+page.server.ts @@ -3,15 +3,88 @@ import type { PageServerLoad } from './$types'; export const load = (async () => { const timestamp = new Date(); - const problems = await db.problem.findMany(); - const teams = await db.team.findMany(); - return { + const contests = await db.contest.findMany({ + include: { problems: true, teams: { include: { submissions: true } } } + }); + const data = { timestamp: timestamp, - problems: problems.map((row) => { - return { friendlyName: row.friendlyName }; - }), - teams: teams.map((row) => { - return { name: row.name }; + contests: contests.map((contest) => { + return { + 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; + })() + }; + }) + }; + }) + }; }) }; + return data; }) satisfies PageServerLoad; diff --git a/web/src/routes/admin/scoreboard/+page.svelte b/web/src/routes/admin/scoreboard/+page.svelte index 62efbd3..ecc9b02 100644 --- a/web/src/routes/admin/scoreboard/+page.svelte +++ b/web/src/routes/admin/scoreboard/+page.svelte @@ -25,38 +25,85 @@ Admin Scoreboard -

    Admin Scoreboard

    +

    Admin Scoreboards

    -
    -
    - {#if updating} -
    - {/if} - Last Updated: {data.timestamp.toLocaleTimeString()} -
    +
    + {#if updating} +
    + {/if} + Last Updated: {data.timestamp.toLocaleTimeString()}
    - - - - - {#each data.problems as problem} - - {/each} - - - - - - {#each data.teams as team} +{#each data.contests as contest} +

    {contest.name}

    +
    +
    +
    + +
    Team Name{problem.friendlyName}Total CorrectTotal Points
    + - - {#each data.problems as _} - + + + + + {#each contest.problems as problem} + {/each} - - - {/each} - -
    {team.name}-/-PlaceTeam NameSolvesTime{problem.friendlyName}00
    + + + {#each contest.teams as team, i} + + {i + 1} + {team.name} + {team.solves} + {team.time.toFixed(0)} + {#each contest.problems as problem} + +
    +
    + {#if team.problems.find((p) => { + return p.id === problem.id; + })?.graphic !== null} + { + return p.id === problem.id; + })?.graphic === 'correct' + ? '/correct.png' + : '/incorrect.png'} + alt="check or X" + width="30px" + /> + {/if} +
    +
    + {#if team.problems.find((p) => { + return p.id === problem.id; + })?.attempts !== 0} + {team.problems.find((p) => { + return p.id === problem.id; + })?.attempts} + {team.problems.find((p) => { + return p.id === problem.id; + })?.attempts === 1 + ? 'Attempt' + : 'Attempts'}
    {#if team.problems.find((p) => { + return p.id === problem.id; + })?.min}{team.problems + .find((p) => { + return p.id === problem.id; + }) + ?.min?.toFixed(0)} min{/if} + {/if} +
    +
    + + {/each} + + {/each} + + +{/each} diff --git a/web/src/routes/admin/submissions/[submissionId]/+page.server.ts b/web/src/routes/admin/submissions/[submissionId]/+page.server.ts index 98b5202..4a86ec2 100644 --- a/web/src/routes/admin/submissions/[submissionId]/+page.server.ts +++ b/web/src/routes/admin/submissions/[submissionId]/+page.server.ts @@ -1,8 +1,6 @@ import { error, redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import type { Actions, PageServerLoad } from './$types'; import { db } from '$lib/server/prisma'; -import * as Diff from 'diff'; -import { SubmissionState } from '@prisma/client'; export const load = (async ({ params }) => { const submissionId = parseInt(params.submissionId); @@ -21,16 +19,6 @@ export const load = (async ({ params }) => { if (!problem) { throw error(500, 'Invalid problem'); } - let diff: string | null = null; - if (submission.state == SubmissionState.Incorrect) { - diff = Diff.createTwoFilesPatch( - 'expected', - 'actual', - problem.realOutput, - submission.actualOutput! - ); - } - return { id: submission.id, state: submission.state, @@ -39,6 +27,18 @@ export const load = (async ({ params }) => { submitTime: submission.createdAt, gradedTime: submission.gradedAt, message: submission.message, - diff: diff + diff: submission.diff }; }) satisfies PageServerLoad; + +export const actions = { + delete: async ({ params }) => { + const submissionId = parseInt(params.submissionId); + try { + await db.submission.delete({ where: { id: submissionId } }); + } catch { + return { success: false }; + } + throw redirect(302, '/admin/submissions'); + } +} satisfies Actions; diff --git a/web/src/routes/admin/submissions/[submissionId]/+page.svelte b/web/src/routes/admin/submissions/[submissionId]/+page.svelte index 4c39039..d009239 100644 --- a/web/src/routes/admin/submissions/[submissionId]/+page.svelte +++ b/web/src/routes/admin/submissions/[submissionId]/+page.svelte @@ -1,22 +1,27 @@ @@ -27,7 +32,31 @@

    Submission

    -All Submissions +{#if form && !form.success} +
    Error
    +{/if} + +
    + +
    + { + if (!confirm('Are you sure?')) { + cancel(); + } + return async ({ update }) => { + update(); + }; + }} + > + + +
    +
    diff --git a/web/src/routes/api/submission/+server.ts b/web/src/routes/api/submission/+server.ts index bb455e5..d52053c 100644 --- a/web/src/routes/api/submission/+server.ts +++ b/web/src/routes/api/submission/+server.ts @@ -64,8 +64,8 @@ export const POST = (async ({ request }) => { const diff = Diff.createTwoFilesPatch( 'expected', 'actual', - data.data.output, - submission.actualOutput! + submission.problem.realOutput, + data.data.output ); await db.submission.update({ where: { id: data.data.submissionId }, diff --git a/web/static/correct.png b/web/static/correct.png new file mode 100644 index 0000000..02bea8a Binary files /dev/null and b/web/static/correct.png differ diff --git a/web/static/incorrect.png b/web/static/incorrect.png new file mode 100644 index 0000000..67aa69e Binary files /dev/null and b/web/static/incorrect.png differ