[web] Format and add CPP to db

This commit is contained in:
orosmatthew 2024-01-16 17:20:21 -05:00
parent 6b955857bf
commit d2d686133c
5 changed files with 104 additions and 86 deletions

View File

@ -37,22 +37,22 @@ enum SubmissionStateReason {
} }
model Submission { model Submission {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
gradedAt DateTime? gradedAt DateTime?
state SubmissionState state SubmissionState
stateReason SubmissionStateReason? stateReason SubmissionStateReason?
stateReasonDetails String? stateReasonDetails String?
actualOutput String? actualOutput String?
commitHash String commitHash String
diff String? diff String?
message String? message String?
team Team @relation(fields: [teamId], references: [id]) team Team @relation(fields: [teamId], references: [id])
teamId Int teamId Int
problem Problem @relation(fields: [problemId], references: [id]) problem Problem @relation(fields: [problemId], references: [id])
problemId Int problemId Int
contestId Int contestId Int
contest Contest @relation(fields: [contestId], references: [id]) contest Contest @relation(fields: [contestId], references: [id])
} }
model Problem { model Problem {
@ -80,6 +80,7 @@ model Team {
enum Language { enum Language {
Java Java
CSharp CSharp
CPP
} }
model ActiveTeam { model ActiveTeam {

View File

@ -36,7 +36,8 @@ export const actions = {
} }
try { try {
await db.contest.delete({ where: { id: parseInt(params.contestId) } }); await db.contest.delete({ where: { id: parseInt(params.contestId) } });
} catch { } catch (e) {
console.error(e);
return { success: false }; return { success: false };
} }
redirect(302, '/admin/contests'); redirect(302, '/admin/contests');

View File

@ -4,37 +4,37 @@ import type { Actions, PageServerLoad } from './$types';
import { fail, redirect } from '@sveltejs/kit'; import { fail, redirect } from '@sveltejs/kit';
import { genPassword } from '../../teams/util'; import { genPassword } from '../../teams/util';
export const load = (async () => { }) satisfies PageServerLoad; export const load = (async () => {}) satisfies PageServerLoad;
export type ContestImportData = { export type ContestImportData = {
Name: string, Name: string;
Problems: ProblemImportData[], Problems: ProblemImportData[];
Teams: TeamImportData[], Teams: TeamImportData[];
Submissions: SubmissionImportData[] Submissions: SubmissionImportData[];
} };
export type ProblemImportData = { export type ProblemImportData = {
ProblemName: string, ProblemName: string;
ShortName: string, ShortName: string;
SampleInput: string, SampleInput: string;
SampleOutput: string, SampleOutput: string;
RealInput: string, RealInput: string;
RealOutput: string RealOutput: string;
} };
export type TeamImportData = { export type TeamImportData = {
TeamName: string TeamName: string;
} };
export type SubmissionImportData = { export type SubmissionImportData = {
TeamName: string, TeamName: string;
ProblemShortName: string, ProblemShortName: string;
State: string, State: string;
SubmitTime: number, SubmitTime: number;
TeamOutput: string, TeamOutput: string;
Code: string | null, Code: string | null;
Language: "Java" | "C#" | "C++" | null Language: 'Java' | 'C#' | 'C++' | null;
} };
export const actions = { export const actions = {
default: async ({ request }) => { default: async ({ request }) => {
@ -45,14 +45,13 @@ export const actions = {
const formData = await request.formData(); const formData = await request.formData();
const contestJson = formData.get('jsonText')?.toString(); const contestJson = formData.get('jsonText')?.toString();
if (!contestJson) { if (!contestJson) {
return fail(400, { message: "Could not get json text" }); return fail(400, { message: 'Could not get json text' });
} }
parsedContest = JSON.parse(contestJson); parsedContest = JSON.parse(contestJson);
includeSubmissions = formData.get('includeSubmissions')?.toString() == "on"; includeSubmissions = formData.get('includeSubmissions')?.toString() == 'on';
} } catch (err) {
catch (err) { return fail(400, { message: 'Could not parse contest data: ' + err?.toString() });
return fail(400, { message: "Could not parse contest data: " + err?.toString() });
} }
try { try {
@ -62,7 +61,9 @@ export const actions = {
if (includeSubmissions && parsedContest.Submissions.length > 0) { if (includeSubmissions && parsedContest.Submissions.length > 0) {
hasSubmissions = true; hasSubmissions = true;
const maxSubmitTimeMinutes = Math.max(...parsedContest.Submissions.map(s => s.SubmitTime)); const maxSubmitTimeMinutes = Math.max(
...parsedContest.Submissions.map((s) => s.SubmitTime)
);
const now = new Date(); const now = new Date();
contestStart = new Date(now.getTime() - maxSubmitTimeMinutes * 60 * 1000); contestStart = new Date(now.getTime() - maxSubmitTimeMinutes * 60 * 1000);
} }
@ -73,24 +74,24 @@ export const actions = {
name: parsedContest.Name, name: parsedContest.Name,
startTime: contestStart, startTime: contestStart,
teams: { teams: {
connectOrCreate: parsedContest.Teams.map(team => ({ connectOrCreate: parsedContest.Teams.map((team) => ({
where: { name: team.TeamName }, where: { name: team.TeamName },
create: { create: {
name: team.TeamName, name: team.TeamName,
password: genPassword(), password: genPassword(),
language: inferTeamLanguage(parsedContest, team) ?? Language.Java language: inferTeamLanguage(parsedContest, team) ?? Language.Java
} }
})), }))
}, },
problems: { problems: {
connectOrCreate: parsedContest.Problems.map(problem => ({ connectOrCreate: parsedContest.Problems.map((problem) => ({
where: { where: {
friendlyName: problem.ProblemName, friendlyName: problem.ProblemName,
pascalName: problem.ShortName, pascalName: problem.ShortName,
sampleInput: problem.SampleInput, sampleInput: problem.SampleInput,
sampleOutput: problem.SampleOutput, sampleOutput: problem.SampleOutput,
realInput: problem.RealInput, realInput: problem.RealInput,
realOutput: problem.RealOutput, realOutput: problem.RealOutput
}, },
create: { create: {
friendlyName: problem.ProblemName, friendlyName: problem.ProblemName,
@ -100,68 +101,75 @@ export const actions = {
realInput: problem.RealInput, realInput: problem.RealInput,
realOutput: problem.RealOutput realOutput: problem.RealOutput
} }
})), }))
}, },
submissions: { submissions: {
create: hasSubmissions create: hasSubmissions
? parsedContest.Submissions.toSorted((a, b) => a.SubmitTime - b.SubmitTime).map(submission => ({ ? parsedContest.Submissions.toSorted((a, b) => a.SubmitTime - b.SubmitTime).map(
createdAt: dateFromContestMinutes(contestStart!, submission.SubmitTime), (submission) => ({
gradedAt: dateFromContestMinutes(contestStart!, submission.SubmitTime + 1), createdAt: dateFromContestMinutes(contestStart!, submission.SubmitTime),
state: convertSubmissionState(submission), gradedAt: dateFromContestMinutes(contestStart!, submission.SubmitTime + 1),
actualOutput: submission.TeamOutput, state: convertSubmissionState(submission),
commitHash: "", actualOutput: submission.TeamOutput,
problem: { commitHash: '',
connect: { problem: {
pascalName: submission.ProblemShortName connect: {
} pascalName: submission.ProblemShortName
}, }
team: { },
connect: { team: {
name: submission.TeamName connect: {
} name: submission.TeamName
} }
})) }
})
)
: [] : []
} }
} }
}); });
} catch (err) { } catch (err) {
return fail(400, { message: "Error updating database: " + err?.toString() }); return fail(400, { message: 'Error updating database: ' + err?.toString() });
} }
return redirect(303, "/admin/contests"); return redirect(303, '/admin/contests');
} }
} satisfies Actions; } satisfies Actions;
function convertSubmissionState(submission: SubmissionImportData): SubmissionState { function convertSubmissionState(submission: SubmissionImportData): SubmissionState {
switch (submission.State) { switch (submission.State) {
case "Correct": case 'Correct':
return SubmissionState.Correct; return SubmissionState.Correct;
case "Incorrect": case 'Incorrect':
return SubmissionState.Incorrect; return SubmissionState.Incorrect;
default: default:
return SubmissionState.InReview; return SubmissionState.InReview;
} }
} }
function inferTeamLanguage(parsedContest: ContestImportData, team: TeamImportData): Language | null { function inferTeamLanguage(
const submissionWithCode = parsedContest.Submissions.find(s => s.TeamName == team.TeamName && s.Code != null); parsedContest: ContestImportData,
team: TeamImportData
): Language | null {
const submissionWithCode = parsedContest.Submissions.find(
(s) => s.TeamName == team.TeamName && s.Code != null
);
if (!submissionWithCode) { if (!submissionWithCode) {
return null; return null;
} }
switch (submissionWithCode.Language) { switch (submissionWithCode.Language) {
case "Java": case 'Java':
return Language.Java; return Language.Java;
case "C#": case 'C#':
return Language.CSharp; return Language.CSharp;
case "C++": case 'C++':
return Language.CPP; return Language.CPP;
default: default:
throw new Error("Unrecognized language: " + submissionWithCode.Language); throw new Error('Unrecognized language: ' + submissionWithCode.Language);
} }
} }
function dateFromContestMinutes(contestStart: Date, minutesFromStart: number): Date { function dateFromContestMinutes(contestStart: Date, minutesFromStart: number): Date {
return new Date(contestStart.getTime() + minutesFromStart * 60 * 1000); return new Date(contestStart.getTime() + minutesFromStart * 60 * 1000);
} }

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import FormAlert from '$lib/FormAlert.svelte'; import FormAlert from '$lib/FormAlert.svelte';
import type { Actions } from './$types'; import type { Actions } from './$types';
@ -48,7 +49,7 @@
<FormAlert /> <FormAlert />
<form method="POST"> <form method="POST" use:enhance>
<div class="mb-4"> <div class="mb-4">
<h3>Contest JSON:</h3> <h3>Contest JSON:</h3>
<textarea <textarea
@ -57,7 +58,9 @@
class="form-control" class="form-control"
rows="10" rows="10"
bind:value={jsonText} bind:value={jsonText}
style="{parsesCorrectly == null ? "" : `border: 2px solid ${parsesCorrectly ? 'green' : 'red'}`}" style={parsesCorrectly == null
? ''
: `border: 2px solid ${parsesCorrectly ? 'green' : 'red'}`}
/> />
</div> </div>
@ -65,9 +68,9 @@
<h3>Import Info:</h3> <h3>Import Info:</h3>
<span>{numTeams ?? 'No'} Teams</span><br /> <span>{numTeams ?? 'No'} Teams</span><br />
<span>{numProblems ?? 'No'} Problems</span><br /> <span>{numProblems ?? 'No'} Problems</span><br />
<span>{numSubmissions ?? 'No'} Submissions</span> <span>{numSubmissions ?? 'No'} Submissions</span>
(<input type="checkbox" checked name="includeSubmissions" id="includeSubmissions" /> (<input type="checkbox" checked name="includeSubmissions" id="includeSubmissions" />
<label id="includeSubmissionsLabel" for="includeSubmissions">Include</label>) <label id="includeSubmissionsLabel" for="includeSubmissions">Include</label>)
</div> </div>
<div class="d-flex flex-row justify-content-end gap-2 m-2"> <div class="d-flex flex-row justify-content-end gap-2 m-2">

View File

@ -73,7 +73,12 @@
<textarea use:stretchTextarea class="code mb-3 form-control" disabled>{data.output}</textarea> <textarea use:stretchTextarea class="code mb-3 form-control" disabled>{data.output}</textarea>
<h3>Diff</h3> <h3>Diff</h3>
<div class="mt-3" id="diff" class:d2h-dark-color-scheme={$theme === 'dark'} class:d2h-light-color-scheme={$theme === 'light'}/> <div
class="mt-3"
id="diff"
class:d2h-dark-color-scheme={$theme === 'dark'}
class:d2h-light-color-scheme={$theme === 'light'}
/>
<form method="POST" action="?/submit" use:enhance> <form method="POST" action="?/submit" use:enhance>
<h5>Message</h5> <h5>Message</h5>