diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile new file mode 100644 index 0000000..4974209 --- /dev/null +++ b/sandbox/Dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:22.04 + +WORKDIR /app + +RUN apt-get update + +RUN apt-get install curl -y + +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs + +RUN apt-get install git -y + +RUN git config --global user.name "Admin" + +RUN git config --global user.email noemail@example.com + +WORKDIR /opt + +RUN apt-get install wget -y + +RUN wget -O java.tar.gz https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.7%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.7_7.tar.gz + +RUN tar -xzvf java.tar.gz + +RUN rm java.tar.gz + +ENV JAVA_PATH=/opt/jdk-17.0.7+7/bin + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +RUN npm run build + +CMD ["node", "dist"] \ No newline at end of file diff --git a/sandbox/docker-compose.yml b/sandbox/docker-compose.yml new file mode 100644 index 0000000..f0713e3 --- /dev/null +++ b/sandbox/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' +services: + sandbox: + build: . + environment: + - ADMIN_URL=http://localhost:5173 + - REPO_URL=http://localhost:7006 + network_mode: 'host' diff --git a/sandbox/src/index.ts b/sandbox/src/index.ts index 3c9deba..a35d865 100644 --- a/sandbox/src/index.ts +++ b/sandbox/src/index.ts @@ -65,13 +65,18 @@ async function cloneAndRun(submissionData: SubmissionGetData) { ); await git.checkout(submissionData.submission.commitHash); const problemName = submissionData.submission.problem.pascalName; - const output = await runJava( - javaBinPath, - buildDir, - join(repoDir, problemName, problemName + '.java'), - problemName, - submissionData.submission.problem.realInput - ); + let output: string; + try { + output = await runJava( + javaBinPath, + buildDir, + join(repoDir, problemName, problemName + '.java'), + problemName, + submissionData.submission.problem.realInput + ); + } catch (error) { + output = `[An error occurred while running]\n${error}`; + } const res = await fetch(urlJoin(adminUrl, 'api/submission'), { method: 'POST', @@ -106,7 +111,14 @@ const repoUrl = process.env.REPO_URL as string; const javaBinPath = process.env.JAVA_PATH as string; async function loop() { - const submissionData = await fetchQueuedSubmission(); + let submissionData: SubmissionGetData | undefined; + try { + submissionData = await fetchQueuedSubmission(); + } catch { + console.error('Failed to fetch submission'); + return; + } + if (!submissionData) { console.error('Unable to fetch submission data'); } else { @@ -121,7 +133,7 @@ async function loop() { async function run() { while (true) { await loop(); - await new Promise((resolve) => setTimeout(resolve, 15000)); + await new Promise((resolve) => setTimeout(resolve, 10000)); } } diff --git a/sandbox/src/run/java.ts b/sandbox/src/run/java.ts index c9ab573..990aeda 100644 --- a/sandbox/src/run/java.ts +++ b/sandbox/src/run/java.ts @@ -1,8 +1,5 @@ -import fs from 'fs-extra'; import { join } from 'path'; -import os from 'os'; import { exec, spawn } from 'child_process'; -import { error } from 'console'; import util from 'util'; const execPromise = util.promisify(exec); @@ -36,8 +33,22 @@ export async function runJava( child.stdin.write(input); child.stdin.end(); + let resolved = false; + child.on('close', () => { - resolve(outputBuffer); + if (!resolved) { + resolved = true; + resolve(outputBuffer); + } }); + + setTimeout(() => { + if (!resolved) { + console.log('30 seconds reached, killing process'); + resolved = true; + child.kill('SIGKILL'); + resolve(outputBuffer + '\n[Timeout after 30 seconds]'); + } + }, 30000); }); } 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/lib/util.ts b/web/src/lib/util.ts new file mode 100644 index 0000000..c54963a --- /dev/null +++ b/web/src/lib/util.ts @@ -0,0 +1,3 @@ +export function stretchTextarea(textarea: HTMLTextAreaElement) { + textarea.style.height = textarea.scrollHeight + 'px'; +} 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..22557cb 100644 --- a/web/src/routes/admin/contests/[contestId]/+page.server.ts +++ b/web/src/routes/admin/contests/[contestId]/+page.server.ts @@ -64,10 +64,14 @@ export const actions = { return { success: false }; } + await db.submission.deleteMany({ where: { contestId: contest.id } }); + contest.teams.forEach(async (team) => { 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/contests/[contestId]/+page.svelte b/web/src/routes/admin/contests/[contestId]/+page.svelte index 8a4958e..8800d79 100644 --- a/web/src/routes/admin/contests/[contestId]/+page.svelte +++ b/web/src/routes/admin/contests/[contestId]/+page.svelte @@ -1,5 +1,6 @@ + +{#each data.teams as team} + + + + + + + + + + +
    IDTeam NamePassword
    {team.id}{team.name}{team.password}
    +{/each} diff --git a/web/src/routes/admin/contests/create/+page.server.ts b/web/src/routes/admin/contests/create/+page.server.ts index 343641e..dd670db 100644 --- a/web/src/routes/admin/contests/create/+page.server.ts +++ b/web/src/routes/admin/contests/create/+page.server.ts @@ -1,8 +1,5 @@ import { db } from '$lib/server/prisma'; -import path, { join } from 'path'; import type { Actions, PageServerLoad } from './$types'; -import fs from 'fs'; -import { simpleGit } from 'simple-git'; import { createRepos } from '../util'; export const load = (async () => { @@ -18,22 +15,6 @@ export const load = (async () => { }; }) satisfies PageServerLoad; -function copyFolderSync(source: string, target: string) { - if (!fs.existsSync(target)) { - fs.mkdirSync(target); - } - - fs.readdirSync(source).forEach((file) => { - const sourcePath = path.join(source, file); - const targetPath = path.join(target, file); - - if (fs.lstatSync(sourcePath).isDirectory()) { - copyFolderSync(sourcePath, targetPath); - } else { - fs.copyFileSync(sourcePath, targetPath); - } - }); -} export const actions = { create: async ({ request }) => { const data = await request.formData(); diff --git a/web/src/routes/admin/diff/[submissionId]/+page.server.ts b/web/src/routes/admin/diff/[submissionId]/+page.server.ts index fda6c42..5cad745 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, output: submission.actualOutput }; }) 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..83fcd5d 100644 --- a/web/src/routes/admin/diff/[submissionId]/+page.svelte +++ b/web/src/routes/admin/diff/[submissionId]/+page.svelte @@ -5,6 +5,7 @@ import type { Actions, PageData } from './$types'; import { enhance } from '$app/forms'; import { goto } from '$app/navigation'; + import { stretchTextarea } from '$lib/util'; export let data: PageData; export let form: Actions; @@ -28,15 +29,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; @@ -50,10 +53,10 @@ - Diff + Review Submission -

    Diff

    +

    Review Submission

    {#if form && !form.success}
    Submission was not successful
    @@ -61,7 +64,17 @@
    Success!
    {/if} -All Reviews +
    + All Reviews + Go to Submission +
    + +

    Output

    + + +

    Diff

    diff --git a/web/src/routes/admin/problems/[problemId]/+page.svelte b/web/src/routes/admin/problems/[problemId]/+page.svelte index f8f9218..a3fef4d 100644 --- a/web/src/routes/admin/problems/[problemId]/+page.svelte +++ b/web/src/routes/admin/problems/[problemId]/+page.svelte @@ -1,6 +1,7 @@ @@ -10,6 +29,13 @@

    Reviews

    +
    + {#if updating} +
    + {/if} + Last Updated: {data.timestamp.toLocaleTimeString()} +
    +
      {#if data.reviewList.length === 0}
      No Submission to Review!
      @@ -20,66 +46,3 @@ > {/each}
    - - diff --git a/web/src/routes/admin/scoreboard/+page.server.ts b/web/src/routes/admin/scoreboard/+page.server.ts index f6a555c..e87f854 100644 --- a/web/src/routes/admin/scoreboard/+page.server.ts +++ b/web/src/routes/admin/scoreboard/+page.server.ts @@ -3,15 +3,106 @@ 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; + })() + }; + }) + }; + }) + .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/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/+page.server.ts b/web/src/routes/admin/submissions/+page.server.ts index a31b7a6..2067a22 100644 --- a/web/src/routes/admin/submissions/+page.server.ts +++ b/web/src/routes/admin/submissions/+page.server.ts @@ -6,6 +6,7 @@ export const load = (async () => { const problems = await db.problem.findMany(); const teams = await db.team.findMany(); return { + timestamp: new Date(), submissions: submissions.map((row) => { return { id: row.id, diff --git a/web/src/routes/admin/submissions/+page.svelte b/web/src/routes/admin/submissions/+page.svelte index e86a3f9..138c672 100644 --- a/web/src/routes/admin/submissions/+page.svelte +++ b/web/src/routes/admin/submissions/+page.svelte @@ -1,12 +1,30 @@ @@ -15,7 +33,17 @@

    Submissions

    -

    Rows are color coded: Red - Incorrect, Green - Correct, Yellow - In Review

    +
    +
    +

    Rows are color coded: Red - Incorrect, Green - Correct, Yellow - In Review

    +
    +
    + {#if updating} +
    + {/if} + Last Updated: {data.timestamp.toLocaleTimeString()} +
    +
    diff --git a/web/src/routes/admin/submissions/[submissionId]/+page.server.ts b/web/src/routes/admin/submissions/[submissionId]/+page.server.ts index 8fe283c..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/admin/teams/+page.server.ts b/web/src/routes/admin/teams/+page.server.ts index d5c62e4..458880a 100644 --- a/web/src/routes/admin/teams/+page.server.ts +++ b/web/src/routes/admin/teams/+page.server.ts @@ -1,5 +1,6 @@ import { db } from '$lib/server/prisma'; import type { Actions, PageServerLoad } from './$types'; +import { genPassword } from './util'; export const load = (async () => { const teams = await db.team.findMany(); @@ -18,7 +19,7 @@ export const actions = { return { success: false }; } try { - await db.team.create({ data: { name: name.toString(), password: "thing" } }); + await db.team.create({ data: { name: name.toString(), password: genPassword() } }); } catch { return { success: false }; } diff --git a/web/src/routes/admin/teams/[teamId]/+page.svelte b/web/src/routes/admin/teams/[teamId]/+page.svelte index 5ba8203..6d26c86 100644 --- a/web/src/routes/admin/teams/[teamId]/+page.svelte +++ b/web/src/routes/admin/teams/[teamId]/+page.svelte @@ -1,5 +1,6 @@ @@ -72,7 +78,7 @@ {:else}

    Change Password

    - +
    +
    diff --git a/web/src/routes/admin/teams/util.ts b/web/src/routes/admin/teams/util.ts new file mode 100644 index 0000000..95cc230 --- /dev/null +++ b/web/src/routes/admin/teams/util.ts @@ -0,0 +1,8 @@ +export function genPassword(): string { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let password = ''; + for (let i = 0; i < 8; i++) { + password += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return password; +} 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/src/routes/api/team/[session]/submit/+server.ts b/web/src/routes/api/team/[session]/submit/+server.ts index 1ff71f3..c3aec83 100644 --- a/web/src/routes/api/team/[session]/submit/+server.ts +++ b/web/src/routes/api/team/[session]/submit/+server.ts @@ -13,7 +13,10 @@ export const POST = (async ({ params, request }) => { const sessionToken = params.session; const activeTeam = await db.activeTeam.findUnique({ where: { sessionToken: sessionToken }, - include: { contest: { include: { problems: { select: { id: true } } } } } + include: { + contest: { include: { problems: { select: { id: true } } } }, + team: { include: { submissions: true } } + } }); if (!activeTeam) { throw error(400); @@ -31,6 +34,19 @@ export const POST = (async ({ params, request }) => { throw error(400); } + // Make sure no submission is currently marked correct + const correctSubmissions = activeTeam.team.submissions.filter((submission) => { + return ( + submission.contestId === activeTeam.contestId && + submission.state === 'Correct' && + submission.problemId === data.data.problemId + ); + }).length; + + if (correctSubmissions !== 0) { + return json({ success: false, message: 'Already submitted correct submission' }); + } + await db.submission.create({ data: { state: SubmissionState.Queued, 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