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 @@
-
\ No newline at end of file
+
\ 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}
+
+
+
+ ID |
+ Team Name |
+ Password |
+
+
+ {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
+
+
+Output
+
+
+Diff