Add problems pages
This commit is contained in:
parent
676b771122
commit
80fb1cdb3e
@ -35,3 +35,12 @@ model Submission {
|
||||
actualOutput String
|
||||
message String?
|
||||
}
|
||||
|
||||
model Problem {
|
||||
id Int @id @default(autoincrement())
|
||||
friendlyName String
|
||||
sampleInput String
|
||||
sampleOutput String
|
||||
realInput String
|
||||
realOutput String
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export const handle = (async ({ event, resolve }) => {
|
||||
if (session) {
|
||||
removeExpiredSessions(session.userId);
|
||||
if (!isSessionExpired(session)) {
|
||||
throw redirect(302, '/admin/reviews');
|
||||
throw redirect(302, '/admin');
|
||||
} else {
|
||||
event.cookies.delete('token');
|
||||
const res = resolve(event);
|
||||
|
11
web/src/routes/admin/+layout.svelte
Normal file
11
web/src/routes/admin/+layout.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<header style="font-size: 24px" class="mt-2 d-flex align-items-center justify-content-center">
|
||||
<ul class="nav col-12 col-md-auto justify-content-center">
|
||||
<li><a href="/admin" class="nav-link px-2">Home</a></li>
|
||||
<li><a href="/admin/reviews" class="nav-link px-2">Reviews</a></li>
|
||||
<li><a href="/admin/problems" class="nav-link px-2">Problems</a></li>
|
||||
<li><a href="/logout" class="nav-link px-2" data-sveltekit-preload-data="off">Logout</a></li>
|
||||
</ul>
|
||||
</header>
|
||||
<hr />
|
||||
<slot />
|
||||
<div style="height: 100px" />
|
5
web/src/routes/admin/+page.svelte
Normal file
5
web/src/routes/admin/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<svelte:head>
|
||||
<title>Admin Home</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Admin Home</h1>
|
@ -56,7 +56,7 @@
|
||||
<title>Diff</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="mb-4">Diff</h1>
|
||||
<h1 style="text-align:center" class="mb-4">Diff</h1>
|
||||
|
||||
<a href="/admin/reviews" class="btn btn-outline-primary">Back</a>
|
||||
<div class="mt-3" id="diff" />
|
||||
|
11
web/src/routes/admin/problems/+page.server.ts
Normal file
11
web/src/routes/admin/problems/+page.server.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
const query = await db.problem.findMany();
|
||||
return {
|
||||
problems: query.map((row) => {
|
||||
return { id: row.id, friendlyName: row.friendlyName };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
29
web/src/routes/admin/problems/+page.svelte
Normal file
29
web/src/routes/admin/problems/+page.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Problems</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Problems</h1>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="text-end">
|
||||
<a href="/admin/problems/create" class="btn btn-outline-success">Create</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if data.problems.length === 0}
|
||||
<div class="alert alert-warning">No problems</div>
|
||||
{/if}
|
||||
<div class="list-group">
|
||||
{#each data.problems as problem}
|
||||
<a
|
||||
href={'/admin/problems/' + problem.id.toString()}
|
||||
class="list-group-item list-group-item-action">{problem.friendlyName}</a
|
||||
>
|
||||
{/each}
|
||||
</div>
|
46
web/src/routes/admin/problems/[problemId]/+page.server.ts
Normal file
46
web/src/routes/admin/problems/[problemId]/+page.server.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async ({ params }) => {
|
||||
const problemId = parseInt(params.problemId);
|
||||
if (isNaN(problemId)) {
|
||||
throw error(400, 'Invalid request');
|
||||
}
|
||||
const query = await db.problem.findUnique({ where: { id: problemId } });
|
||||
if (!query) {
|
||||
throw redirect(302, '/admin/problems');
|
||||
}
|
||||
return { problemData: query };
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
export const actions = {
|
||||
edit: async ({ params, request }) => {
|
||||
const problemId = parseInt(params.problemId);
|
||||
if (isNaN(problemId)) {
|
||||
throw error(400, 'Invalid problem');
|
||||
}
|
||||
const data = await request.formData();
|
||||
const name = data.get('name');
|
||||
const sampleInput = data.get('sampleInput');
|
||||
const sampleOutput = data.get('sampleOutput');
|
||||
const realInput = data.get('realInput');
|
||||
const realOutput = data.get('realOutput');
|
||||
if (!name || !sampleInput || !sampleOutput || !realInput || !realOutput) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
await db.problem.update({
|
||||
where: { id: problemId },
|
||||
data: {
|
||||
friendlyName: name.toString(),
|
||||
sampleInput: sampleInput.toString(),
|
||||
sampleOutput: sampleOutput.toString(),
|
||||
realInput: realInput.toString(),
|
||||
realOutput: realOutput.toString()
|
||||
}
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
} satisfies Actions;
|
112
web/src/routes/admin/problems/[problemId]/+page.svelte
Normal file
112
web/src/routes/admin/problems/[problemId]/+page.svelte
Normal file
@ -0,0 +1,112 @@
|
||||
<script lang="ts">
|
||||
import type { Actions, PageData } from './$types';
|
||||
|
||||
let editing = false;
|
||||
|
||||
export let data: PageData;
|
||||
export let form: Actions;
|
||||
|
||||
function stretchTextarea(textarea: HTMLTextAreaElement) {
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">{data.problemData.friendlyName}</h1>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<a href="/admin/problems" class="btn btn-outline-primary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
{#if !editing}
|
||||
<button
|
||||
on:click={() => {
|
||||
if (!editing) {
|
||||
editing = true;
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
class="btn btn-warning">Edit</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if form && !form.success}
|
||||
<div class="mt-3 alert alert-danger">Invalid edit</div>
|
||||
{/if}
|
||||
|
||||
<form method="POST" action="?/edit">
|
||||
<h4 style="text-align:center" class="mt-3">Name</h4>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-auto">
|
||||
<textarea
|
||||
name="name"
|
||||
style="height:auto"
|
||||
class="form-control"
|
||||
disabled={!editing}
|
||||
use:stretchTextarea>{data.problemData.friendlyName}</textarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<h4 style="text-align:center" class="mt-5">Sample Data</h4>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5>Input</h5>
|
||||
<textarea
|
||||
name="sampleInput"
|
||||
style="height:auto"
|
||||
class="form-control"
|
||||
disabled={!editing}
|
||||
use:stretchTextarea>{data.problemData.sampleInput}</textarea
|
||||
>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5>Output</h5>
|
||||
<textarea
|
||||
name="sampleOutput"
|
||||
style="height:auto"
|
||||
class="form-control"
|
||||
disabled={!editing}
|
||||
use:stretchTextarea>{data.problemData.sampleOutput}</textarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style="text-align:center" class="mt-5">Real Data</h4>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5>Input</h5>
|
||||
<textarea
|
||||
name="realInput"
|
||||
style="height:auto"
|
||||
class="form-control"
|
||||
disabled={!editing}
|
||||
use:stretchTextarea>{data.problemData.realInput}</textarea
|
||||
>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5>Output</h5>
|
||||
<textarea
|
||||
name="realOutput"
|
||||
style="height:auto"
|
||||
class="form-control"
|
||||
disabled={!editing}
|
||||
use:stretchTextarea>{data.problemData.realOutput}</textarea
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#if editing}
|
||||
<div class="mt-3 row">
|
||||
<div class="text-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
on:click={async () => {
|
||||
location.reload();
|
||||
}}>Cancel</button
|
||||
>
|
||||
<button type="submit" class="btn btn-success">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
28
web/src/routes/admin/problems/create/+page.server.ts
Normal file
28
web/src/routes/admin/problems/create/+page.server.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
export const actions = {
|
||||
create: async ({ request }) => {
|
||||
const data = await request.formData();
|
||||
const name = data.get('name');
|
||||
const sampleInput = data.get('sampleInput');
|
||||
const sampleOutput = data.get('sampleOutput');
|
||||
const realInput = data.get('realInput');
|
||||
const realOutput = data.get('realOutput');
|
||||
if (!name || !sampleInput || !sampleOutput || !realInput || !realOutput) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
await db.problem.create({
|
||||
data: {
|
||||
friendlyName: name.toString(),
|
||||
sampleInput: sampleInput.toString(),
|
||||
sampleOutput: sampleOutput.toString(),
|
||||
realInput: realInput.toString(),
|
||||
realOutput: realOutput.toString()
|
||||
}
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
} satisfies Actions;
|
64
web/src/routes/admin/problems/create/+page.svelte
Normal file
64
web/src/routes/admin/problems/create/+page.svelte
Normal file
@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Actions } from './$types';
|
||||
|
||||
export let form: Actions;
|
||||
|
||||
$: if (form && form.success) {
|
||||
goto('/admin/problems');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Create Problem</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Create Problem</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<a href="/admin/problems" class="btn btn-outline-primary">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if form && !form.success}
|
||||
<div class="mt-3 alert alert-danger">Invalid data</div>
|
||||
{/if}
|
||||
|
||||
<form method="POST" action="?/create" use:enhance>
|
||||
<h4 style="text-align:center" class="mt-3">Name</h4>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-auto">
|
||||
<textarea name="name" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<h4 style="text-align:center" class="mt-5">Sample Data</h4>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5>Input</h5>
|
||||
<textarea name="sampleInput" class="form-control" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5>Output</h5>
|
||||
<textarea name="sampleOutput" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style="text-align:center" class="mt-5">Real Data</h4>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h5>Input</h5>
|
||||
<textarea name="realInput" class="form-control" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h5>Output</h5>
|
||||
<textarea name="realOutput" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 row">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
@ -7,7 +7,11 @@ export const load = (async () => {
|
||||
query.sort((a, b) => {
|
||||
return a.createdAt.valueOf() - b.createdAt.valueOf();
|
||||
});
|
||||
return { reviewList: query };
|
||||
return {
|
||||
reviewList: query.map((row) => {
|
||||
return { id: row.id, createdAt: row.createdAt };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
export const actions = {
|
||||
|
@ -10,9 +10,7 @@
|
||||
<title>Reviews</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 class="mb-4">Reviews</h1>
|
||||
|
||||
<a href="/logout" class="mb-2 btn btn-outline-primary" data-sveltekit-preload-data="off">Logout</a>
|
||||
<h1 style="text-align:center" class="mb-4">Reviews</h1>
|
||||
|
||||
<ul class="list-group">
|
||||
{#if data.reviewList.length === 0}
|
||||
|
Loading…
Reference in New Issue
Block a user