[web] Recreate repos and start contest

This commit is contained in:
orosmatthew 2023-05-07 17:32:57 -04:00
parent 87dd37c6a6
commit 85b1a48dd8
10 changed files with 1100 additions and 57 deletions

950
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0", "prettier-plugin-svelte": "^2.10.0",
"prisma-erd-generator": "^1.7.0",
"svelte": "^3.58.0", "svelte": "^3.58.0",
"svelte-check": "^3.2.0", "svelte-check": "^3.2.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",
@ -35,10 +36,12 @@
"dependencies": { "dependencies": {
"@prisma/client": "^4.13.0", "@prisma/client": "^4.13.0",
"@sveltejs/adapter-node": "^1.2.4", "@sveltejs/adapter-node": "^1.2.4",
"@types/fs-extra": "^11.0.1",
"axios": "^1.4.0", "axios": "^1.4.0",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"diff": "^5.1.0", "diff": "^5.1.0",
"diff2html": "^3.4.35", "diff2html": "^3.4.35",
"fs-extra": "^11.1.1",
"highlight.js": "^11.8.0", "highlight.js": "^11.8.0",
"memfs": "^3.5.1", "memfs": "^3.5.1",
"node-git-server": "^1.0.0", "node-git-server": "^1.0.0",

1
web/prisma/ERD.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -2,6 +2,10 @@ generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
} }
generator erd {
provider = "prisma-erd-generator"
}
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")

View File

@ -16,7 +16,7 @@ export function startGitServer() {
const repoDir = 'repo'; const repoDir = 'repo';
repos = new Git(join(repoDir), { repos = new Git(join(repoDir), {
autoCreate: true, autoCreate: true
}); });
repos.on('push', (push) => { repos.on('push', (push) => {

View File

@ -1,6 +1,9 @@
import { error, redirect, type Actions } from '@sveltejs/kit'; import { error, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { db } from '$lib/server/prisma'; import { db } from '$lib/server/prisma';
import fs from 'fs-extra';
import { join } from 'path';
import { createRepos } from '../util';
export const load = (async ({ params }) => { export const load = (async ({ params }) => {
const contestId = parseInt(params.contestId); const contestId = parseInt(params.contestId);
@ -9,7 +12,7 @@ export const load = (async ({ params }) => {
} }
const contest = await db.contest.findUnique({ const contest = await db.contest.findUnique({
where: { id: contestId }, where: { id: contestId },
include: { problems: true, teams: true } include: { problems: true, teams: true, activeTeams: true }
}); });
if (!contest) { if (!contest) {
throw redirect(302, '/admin/contests'); throw redirect(302, '/admin/contests');
@ -17,11 +20,12 @@ export const load = (async ({ params }) => {
return { return {
name: contest.name, name: contest.name,
problems: contest.problems.map((problem) => { problems: contest.problems.map((problem) => {
return { name: problem.friendlyName }; return { id: problem.id, name: problem.friendlyName };
}), }),
teams: contest.teams.map((team) => { teams: contest.teams.map((team) => {
return { name: team.name }; return { id: team.id, name: team.name };
}) }),
activeTeams: contest.activeTeams.length
}; };
}) satisfies PageServerLoad; }) satisfies PageServerLoad;
@ -36,5 +40,68 @@ export const actions = {
return { success: false }; return { success: false };
} }
throw redirect(302, '/admin/contests'); throw redirect(302, '/admin/contests');
},
start: async ({ params }) => {
if (!params.contestId) {
return { success: false };
}
const contestId = parseInt(params.contestId);
if (isNaN(contestId)) {
return { success: false };
}
const contest = await db.contest.findUnique({
where: { id: contestId },
include: { activeTeams: true, teams: { include: { activeTeam: true } } }
});
if (
!contest ||
contest.teams.length === 0 ||
contest.activeTeams.length !== 0 ||
contest.teams.find((team) => {
return team.activeTeam;
})
) {
return { success: false };
}
contest.teams.forEach(async (team) => {
await db.activeTeam.create({ data: { teamId: team.id, contestId: contest.id } });
});
return { success: true };
},
stop: async ({ params }) => {
if (!params.contestId) {
return { success: false };
}
const contestId = parseInt(params.contestId);
if (isNaN(contestId)) {
return { success: false };
}
const contest = await db.contest.findUnique({
where: { id: contestId },
include: { activeTeams: true }
});
if (!contest || contest.activeTeams.length === 0) {
return { success: false };
}
contest.activeTeams.forEach(async (activeTeam) => {
await db.activeTeam.delete({ where: { id: activeTeam.id } });
});
return { success: true };
},
repo: async ({ params }) => {
if (!params.contestId) {
return { success: false };
}
const contestId = parseInt(params.contestId);
if (isNaN(contestId)) {
return { success: false };
}
if (fs.existsSync(join('repo', contestId.toString()))) {
fs.removeSync(join('repo', contestId.toString()));
}
await createRepos(contestId);
return { success: true };
} }
} satisfies Actions; } satisfies Actions;

View File

@ -12,6 +12,10 @@
<h1 style="text-align:center" class="mb-4">{data.name}</h1> <h1 style="text-align:center" class="mb-4">{data.name}</h1>
{#if data.activeTeams !== 0}
<div class="alert alert-success">In Progress</div>
{/if}
{#if form && !form.success} {#if form && !form.success}
<div class="alert alert-danger">An error occured</div> <div class="alert alert-danger">An error occured</div>
{/if} {/if}
@ -20,23 +24,26 @@
<div class="col-6"> <div class="col-6">
<a href="/admin/contests" class="btn btn-outline-primary">All Contests</a> <a href="/admin/contests" class="btn btn-outline-primary">All Contests</a>
</div> </div>
<div class="col-6"> <div class="col-6 text-end">
<div class="text-end"> <form
<form method="POST"
method="POST" use:enhance={({ cancel }) => {
action="?/delete" if (!confirm('Are you sure?')) {
use:enhance={({ cancel }) => { cancel();
if (!confirm('Are you sure?')) { }
cancel(); return async ({ update }) => {
} update();
return async ({ update }) => { };
update(); }}
}; >
}} {#if data.activeTeams === 0}
> <button type="submit" formaction="?/delete" class="btn btn-danger">Delete</button>
<button type="submit" class="btn btn-danger">Delete</button> <button type="submit" formaction="?/repo" class="btn btn-warning">Recreate Repos</button>
</form> <button type="submit" formaction="?/start" class="btn btn-success">Start</button>
</div> {:else}
<button type="submit" formaction="?/stop" class="btn btn-outline-danger">Stop</button>
{/if}
</form>
</div> </div>
</div> </div>
@ -45,7 +52,9 @@
<h4>Teams</h4> <h4>Teams</h4>
<div class="list-group"> <div class="list-group">
{#each data.teams as team} {#each data.teams as team}
<div class="list-group-item">{team.name}</div> <a href={`/admin/teams/${team.id}`} class="list-group-item list-group-item-action"
>{team.name}</a
>
{/each} {/each}
</div> </div>
</div> </div>
@ -53,7 +62,9 @@
<h4>Problems</h4> <h4>Problems</h4>
<div class="list-group"> <div class="list-group">
{#each data.problems as problem} {#each data.problems as problem}
<div class="list-group-item">{problem.name}</div> <a href={`/admin/problems/${problem.id}`} class="list-group-item list-group-item-action"
>{problem.name}</a
>
{/each} {/each}
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@ import path, { join } from 'path';
import type { Actions, PageServerLoad } from './$types'; import type { Actions, PageServerLoad } from './$types';
import fs from 'fs'; import fs from 'fs';
import { simpleGit } from 'simple-git'; import { simpleGit } from 'simple-git';
import { createRepos } from '../util';
export const load = (async () => { export const load = (async () => {
const teams = await db.team.findMany(); const teams = await db.team.findMany();
@ -63,35 +64,8 @@ export const actions = {
include: { teams: true, problems: true } include: { teams: true, problems: true }
}); });
// Create repos await createRepos(createdContest.id);
if (fs.existsSync('temp')) {
fs.rmSync('temp', { recursive: true });
}
fs.mkdirSync('temp');
createdContest.teams.forEach(async (team) => {
fs.mkdirSync(join('temp', team.id.toString()));
const git = simpleGit({ baseDir: join('temp', team.id.toString()) });
await git.init();
await git.checkoutLocalBranch('master');
createdContest.problems.forEach((problem) => {
fs.mkdirSync(join('temp', team.id.toString(), problem.pascalName));
fs.writeFileSync(
join('temp', team.id.toString(), problem.pascalName, problem.pascalName + '.java'),
`public class ${problem.pascalName} {
public static void main(String[] args) {
System.out.println("Hello ${problem.pascalName}!");
}
}`
);
});
await git.add('.');
await git.commit('Initial', { '--author': 'Admin <>' });
await git.push(
'http://localhost:7006/' + createdContest.id.toString() + '/' + team.id.toString(),
'master'
);
});
return { success: true }; return { success: true };
} }
} satisfies Actions; } satisfies Actions;

View File

@ -0,0 +1,41 @@
import { db } from '$lib/server/prisma';
import fs from 'fs-extra';
import { join } from 'path';
import simpleGit from 'simple-git';
export async function createRepos(contestId: number) {
if (fs.existsSync('temp')) {
fs.rmSync('temp', { recursive: true });
}
fs.mkdirSync('temp');
const contest = await db.contest.findUnique({
where: { id: contestId },
include: { teams: true, problems: true }
});
if (!contest) {
return;
}
contest.teams.forEach(async (team) => {
fs.mkdirSync(join('temp', team.id.toString()));
const git = simpleGit({ baseDir: join('temp', team.id.toString()) });
await git.init();
await git.checkoutLocalBranch('master');
contest.problems.forEach((problem) => {
fs.mkdirSync(join('temp', team.id.toString(), problem.pascalName));
fs.writeFileSync(
join('temp', team.id.toString(), problem.pascalName, problem.pascalName + '.java'),
`public class ${problem.pascalName} {
public static void main(String[] args) {
System.out.println("Hello ${problem.pascalName}!");
}
}`
);
});
await git.add('.');
await git.commit('Initial', { '--author': 'Admin <>' });
await git.push(
'http://localhost:7006/' + contest.id.toString() + '/' + team.id.toString(),
'master'
);
});
}

View File

@ -38,7 +38,7 @@
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<a href="/admin/problems" class="btn btn-outline-primary">Back</a> <a href="/admin/problems" class="btn btn-outline-primary">All Problems</a>
</div> </div>
<div class="col-6 text-end"> <div class="col-6 text-end">
<button on:click={deleteProblem} type="button" class="btn btn-danger">Delete</button> <button on:click={deleteProblem} type="button" class="btn btn-danger">Delete</button>