Plaetze in admin

This commit is contained in:
titver968
2025-05-09 09:54:20 +02:00
parent 83ce0c3def
commit 3dd73a98ca
7 changed files with 154 additions and 47 deletions

72
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "praktikum", "name": "praktikum",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@prisma/client": "^6.6.0", "@prisma/client": "^6.7.0",
"@sveltejs/adapter-node": "^5.2.12", "@sveltejs/adapter-node": "^5.2.12",
"bcryptjs": "^3.0.2" "bcryptjs": "^3.0.2"
}, },
@@ -28,7 +28,7 @@
"postcss": "^8.5.3", "postcss": "^8.5.3",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"prisma": "^6.6.0", "prisma": "^6.7.0",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
@@ -859,9 +859,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz",
"integrity": "sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg==", "integrity": "sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@@ -881,9 +881,9 @@
} }
}, },
"node_modules/@prisma/config": { "node_modules/@prisma/config": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz",
"integrity": "sha512-d8FlXRHsx72RbN8nA2QCRORNv5AcUnPXgtPvwhXmYkQSMF/j9cKaJg+9VcUzBRXGy9QBckNzEQDEJZdEOZ+ubA==", "integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -892,53 +892,53 @@
} }
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz",
"integrity": "sha512-DL6n4IKlW5k2LEXzpN60SQ1kP/F6fqaCgU/McgaYsxSf43GZ8lwtmXLke9efS+L1uGmrhtBUP4npV/QKF8s2ZQ==", "integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz",
"integrity": "sha512-nC0IV4NHh7500cozD1fBoTwTD1ydJERndreIjpZr/S3mno3P6tm8qnXmIND5SwUkibNeSJMpgl4gAnlqJ/gVlg==", "integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.6.0", "@prisma/debug": "6.7.0",
"@prisma/engines-version": "6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a", "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"@prisma/fetch-engine": "6.6.0", "@prisma/fetch-engine": "6.7.0",
"@prisma/get-platform": "6.6.0" "@prisma/get-platform": "6.7.0"
} }
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a", "version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz",
"integrity": "sha512-JzRaQ5Em1fuEcbR3nUsMNYaIYrOT1iMheenjCvzZblJcjv/3JIuxXN7RCNT5i6lRkLodW5ojCGhR7n5yvnNKrw==", "integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz",
"integrity": "sha512-Ohfo8gKp05LFLZaBlPUApM0M7k43a0jmo86YY35u1/4t+vuQH9mRGU7jGwVzGFY3v+9edeb/cowb1oG4buM1yw==", "integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.6.0", "@prisma/debug": "6.7.0",
"@prisma/engines-version": "6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a", "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
"@prisma/get-platform": "6.6.0" "@prisma/get-platform": "6.7.0"
} }
}, },
"node_modules/@prisma/get-platform": { "node_modules/@prisma/get-platform": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.6.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz",
"integrity": "sha512-3qCwmnT4Jh5WCGUrkWcc6VZaw0JY7eWN175/pcb5Z6FiLZZ3ygY93UX0WuV41bG51a6JN/oBH0uywJ90Y+V5eA==", "integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.6.0" "@prisma/debug": "6.7.0"
} }
}, },
"node_modules/@rollup/plugin-commonjs": { "node_modules/@rollup/plugin-commonjs": {
@@ -4223,15 +4223,15 @@
} }
}, },
"node_modules/prisma": { "node_modules/prisma": {
"version": "6.6.0", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.6.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
"integrity": "sha512-SYCUykz+1cnl6Ugd8VUvtTQq5+j1Q7C0CtzKPjQ8JyA2ALh0EEJkMCS+KgdnvKW1lrxjtjCyJSHOOT236mENYg==", "integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/config": "6.6.0", "@prisma/config": "6.7.0",
"@prisma/engines": "6.6.0" "@prisma/engines": "6.7.0"
}, },
"bin": { "bin": {
"prisma": "build/index.js" "prisma": "build/index.js"

View File

@@ -32,7 +32,7 @@
"postcss": "^8.5.3", "postcss": "^8.5.3",
"prettier": "^3.4.2", "prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"prisma": "^6.6.0", "prisma": "^6.7.0",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
@@ -43,7 +43,7 @@
"vite-plugin": "^0.0.0" "vite-plugin": "^0.0.0"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.6.0", "@prisma/client": "^6.7.0",
"@sveltejs/adapter-node": "^5.2.12", "@sveltejs/adapter-node": "^5.2.12",
"bcryptjs": "^3.0.2" "bcryptjs": "^3.0.2"
} }

View File

@@ -0,0 +1,20 @@
/*
Warnings:
- Added the required column `plaetze` to the `Dienststelle` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Dienststelle" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"plaetze" INTEGER NOT NULL
);
INSERT INTO "new_Dienststelle" ("id", "name") SELECT "id", "name" FROM "Dienststelle";
DROP TABLE "Dienststelle";
ALTER TABLE "new_Dienststelle" RENAME TO "Dienststelle";
CREATE UNIQUE INDEX "Dienststelle_name_key" ON "Dienststelle"("name");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

Binary file not shown.

View File

@@ -17,6 +17,7 @@ model Admin {
model Dienststelle { model Dienststelle {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
name String @unique name String @unique
plaetze Int
anmeldungenWunsch1 Anmeldung[] @relation("Wunsch1") anmeldungenWunsch1 Anmeldung[] @relation("Wunsch1")
anmeldungenWunsch2 Anmeldung[] @relation("Wunsch2") anmeldungenWunsch2 Anmeldung[] @relation("Wunsch2")
anmeldungenWunsch3 Anmeldung[] @relation("Wunsch3") anmeldungenWunsch3 Anmeldung[] @relation("Wunsch3")

View File

@@ -1,20 +1,22 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let dienststellen: { id: number; name: string }[] = []; let dienststellen: { id: number; name: string; plaetze: number }[] = [];
let neuerName = ''; let neuerName = '';
let neuePlaetze = 0;
let fehlermeldung = ''; let fehlermeldung = '';
let bearbeiteId: number | null = null;
async function ladeDienststellen() { async function ladeDienststellen() {
const res = await fetch('/api/admin/dienststellen'); const res = await fetch('/api/admin/dienststellen');
dienststellen = await res.json(); dienststellen = await res.json();
} }
/*
async function hinzufuegen() { async function hinzufuegen() {
fehlermeldung = ''; fehlermeldung = '';
if (!neuerName.trim()) return; if (!neuerName.trim()) return;
const res = await fetch('/api/admin/dienststellen', { const res = await fetch('/api/admin/dienststellen', {
method: 'POST', method: 'POST',
body: JSON.stringify({ name: neuerName }), body: JSON.stringify({ name: neuerName, plaetze: neuePlaetze }),
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
@@ -26,6 +28,39 @@
fehlermeldung = err.error || 'Fehler beim Hinzufügen'; fehlermeldung = err.error || 'Fehler beim Hinzufügen';
} }
} }
*/
function bearbeiten(d: { id: number; name: string; plaetze: number }) {
neuerName = d.name;
neuePlaetze = d.plaetze;
bearbeiteId = d.id;
}
async function speichern() {
fehlermeldung = '';
if (!neuerName.trim()) return;
const method = bearbeiteId ? 'PATCH' : 'POST';
const body = bearbeiteId
? { id: bearbeiteId, name: neuerName, plaetze: neuePlaetze }
: { name: neuerName, plaetze: neuePlaetze };
const res = await fetch('/api/admin/dienststellen', {
method,
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' }
});
if (res.ok) {
neuerName = '';
neuePlaetze = 0;
bearbeiteId = null;
await ladeDienststellen();
} else {
const err = await res.json();
fehlermeldung = err.error || 'Fehler beim Speichern';
}
}
async function loeschen(id: number) { async function loeschen(id: number) {
if (!confirm('Diese Dienststelle wirklich löschen?')) return; if (!confirm('Diese Dienststelle wirklich löschen?')) return;
@@ -40,8 +75,14 @@
<div class="p-6 max-w-xl mx-auto space-y-6"> <div class="p-6 max-w-xl mx-auto space-y-6">
<h1 class="text-2xl font-bold">Dienststellen verwalten</h1> <h1 class="text-2xl font-bold">Dienststellen verwalten</h1>
<div class="flex gap-2"> <div class="flex gap-2">
<input bind:value={neuerName} placeholder="Neue Dienststelle" class="input w-full" /> <input bind:value={neuerName} placeholder="Dienststelle" class="input w-full" />
<input type="number" bind:value={neuePlaetze} placeholder="Anzahl Plätze" class="input w-1/4" />
<button on:click={() => { neuerName = ''; neuePlaetze = 0; bearbeiteId = null }} class="text-sm text-gray-500 hover:underline">
Zurücksetzen
</button>
<button on:click={speichern} class="bg-blue-600 text-white px-4 py-2 rounded">
{bearbeiteId !== null ? 'Ändern' : 'Hinzufügen'}
</button> </button>
</div> </div>
@@ -52,6 +93,8 @@
<ul class="divide-y border rounded"> <ul class="divide-y border rounded">
{#each dienststellen as d} {#each dienststellen as d}
<li class="flex justify-between items-center p-2"> <li class="flex justify-between items-center p-2">
<span>{d.name}</span>
<span class="text-sm text-gray-500">{d.plaetze} Plätze</span>
<button on:click={() => (neuerName = d.name, neuePlaetze = d.plaetze, bearbeiteId = d.id)} class="text-sm text-blue-600 hover:underline">Bearbeiten</button> <button on:click={() => (neuerName = d.name, neuePlaetze = d.plaetze, bearbeiteId = d.id)} class="text-sm text-blue-600 hover:underline">Bearbeiten</button>
<button on:click={() => loeschen(d.id)} class="text-sm text-red-600 hover:underline">Löschen</button> <button on:click={() => loeschen(d.id)} class="text-sm text-red-600 hover:underline">Löschen</button>
</li> </li>

View File

@@ -4,7 +4,7 @@ import type { RequestHandler } from './$types';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
function checkAuth(cookies: any) { function checkAuth(cookies: unknown) {
return cookies.get('admin_session') === 'true'; return cookies.get('admin_session') === 'true';
} }
@@ -16,15 +16,58 @@ export const GET: RequestHandler = async ({ cookies }) => {
export const POST: RequestHandler = async ({ cookies, request }) => { export const POST: RequestHandler = async ({ cookies, request }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const { name } = await request.json(); const { name, plaetze } = await request.json();
if (typeof plaetze !== 'number' || plaetze < 0) {
return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 });
}
try { try {
const created = await prisma.dienststelle.create({ data: { name } }); const created = await prisma.dienststelle.create({ data: {
name,
plaetze,
} });
return json(created); return json(created);
} catch (e) { } catch (e) {
console.error('Fehler beim Hinzufuegen:', e);
return json({ error: 'Dienststelle existiert bereits' }, { status: 400 }); return json({ error: 'Dienststelle existiert bereits' }, { status: 400 });
} }
}; };
export const PATCH: RequestHandler = async ({ cookies, request }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const { id, name, plaetze } = await request.json();
if (typeof id !== 'number' || isNaN(id) || !name || typeof plaetze !== 'number' || plaetze < 0) {
return json({ error: 'Ungültige Eingabedaten' }, { status: 400 });
}
const existing = await prisma.dienststelle.findUnique({ where: { id } });
if (!existing) {
return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 });
}
const konflikt = await prisma.dienststelle.findFirst({
where: {
name,
NOT: { id },
},
});
if (konflikt) {
return json({ error: 'Eine andere Dienststelle mit diesem Namen existiert bereits' }, { status: 400 });
}
try {
const updated = await prisma.dienststelle.update({
where: { id },
data: { name, plaetze },
});
return json(updated);
} catch (e) {
console.error('Fehler beim Update:', e);
return json({ error: 'Update fehlgeschlagen' }, { status: 400 });
}
};
export const DELETE: RequestHandler = async ({ cookies, url }) => { export const DELETE: RequestHandler = async ({ cookies, url }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const id = Number(url.searchParams.get('id')); const id = Number(url.searchParams.get('id'));