Add problems pages

This commit is contained in:
orosmatthew 2023-04-28 20:06:28 -04:00
parent 676b771122
commit 80fb1cdb3e
13 changed files with 323 additions and 6 deletions

View File

@ -35,3 +35,12 @@ model Submission {
actualOutput String actualOutput String
message String? message String?
} }
model Problem {
id Int @id @default(autoincrement())
friendlyName String
sampleInput String
sampleOutput String
realInput String
realOutput String
}

View File

@ -24,7 +24,7 @@ export const handle = (async ({ event, resolve }) => {
if (session) { if (session) {
removeExpiredSessions(session.userId); removeExpiredSessions(session.userId);
if (!isSessionExpired(session)) { if (!isSessionExpired(session)) {
throw redirect(302, '/admin/reviews'); throw redirect(302, '/admin');
} else { } else {
event.cookies.delete('token'); event.cookies.delete('token');
const res = resolve(event); const res = resolve(event);

View 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" />

View File

@ -0,0 +1,5 @@
<svelte:head>
<title>Admin Home</title>
</svelte:head>
<h1 style="text-align:center" class="mb-4">Admin Home</h1>

View File

@ -56,7 +56,7 @@
<title>Diff</title> <title>Diff</title>
</svelte:head> </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> <a href="/admin/reviews" class="btn btn-outline-primary">Back</a>
<div class="mt-3" id="diff" /> <div class="mt-3" id="diff" />

View 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;

View 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>

View 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;

View 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>

View 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;

View 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>

View File

@ -7,7 +7,11 @@ export const load = (async () => {
query.sort((a, b) => { query.sort((a, b) => {
return a.createdAt.valueOf() - b.createdAt.valueOf(); return a.createdAt.valueOf() - b.createdAt.valueOf();
}); });
return { reviewList: query }; return {
reviewList: query.map((row) => {
return { id: row.id, createdAt: row.createdAt };
})
};
}) satisfies PageServerLoad; }) satisfies PageServerLoad;
export const actions = { export const actions = {

View File

@ -10,9 +10,7 @@
<title>Reviews</title> <title>Reviews</title>
</svelte:head> </svelte:head>
<h1 class="mb-4">Reviews</h1> <h1 style="text-align:center" class="mb-4">Reviews</h1>
<a href="/logout" class="mb-2 btn btn-outline-primary" data-sveltekit-preload-data="off">Logout</a>
<ul class="list-group"> <ul class="list-group">
{#if data.reviewList.length === 0} {#if data.reviewList.length === 0}