diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7b79ae7..4ab2d70 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -38,38 +38,27 @@ enum Status { } model Anmeldung { - id Int @id @default(autoincrement()) - anrede String - vorname String - nachname String - geburtsdatum String - strasse String - hausnummer String - ort String - plz String - telefon String - email String @unique - noteDeutsch Int - noteMathe Int - sozialverhalten String - schulart String - motivation String - - praktikumId Int - - wunsch1 Dienststelle @relation("Wunsch1", fields: [wunsch1Id], references: [id]) - wunsch1Id Int - wunsch2 Dienststelle @relation("Wunsch2", fields: [wunsch2Id], references: [id]) - wunsch2Id Int - wunsch3 Dienststelle @relation("Wunsch3", fields: [wunsch3Id], references: [id]) - wunsch3Id Int - status Status @default(OFFEN) - zugewiesenId Int? - zugewiesen Dienststelle? @relation("Zugewiesen", fields: [zugewiesenId], references: [id]) - - timestamp DateTime @default(now()) - - pdfs PdfDatei[] @relation("AnmeldungPdfs") + id Int @id @default(autoincrement()) + anrede String + vorname String + nachname String + email String + noteDeutsch String? + noteMathe String? + sozialverhalten String? + status Status @default(OFFEN) + zugewiesenId Int? // ID der zugewiesenen Dienststelle + zugewiesen Dienststelle? @relation(fields: [zugewiesenId], references: [id]) + wunsch1Id Int? + wunsch1 Dienststelle? @relation("Wunsch1", fields: [wunsch1Id], references: [id]) + wunsch2Id Int? + wunsch2 Dienststelle? @relation("Wunsch2", fields: [wunsch2Id], references: [id]) + wunsch3Id Int? + wunsch3 Dienststelle? @relation("Wunsch3", fields: [wunsch3Id], references: [id]) + timestamp BigInt + pdfs PdfDatei[] + + @@map("anmeldungen") } model PdfDatei { diff --git a/src/lib/components/AnmeldungenTable.svelte b/src/lib/components/AnmeldungenTable.svelte index 8dfe205..2eb6412 100644 --- a/src/lib/components/AnmeldungenTable.svelte +++ b/src/lib/components/AnmeldungenTable.svelte @@ -2,6 +2,12 @@ @@ -68,101 +76,111 @@ {#each anmeldungen as anmeldung (anmeldung.id)} + -
- {anmeldung.anrede} {anmeldung.vorname} {anmeldung.nachname} +
+
+ {anmeldung.anrede} {anmeldung.vorname} {anmeldung.nachname} +
+
+ {anmeldung.email} +
-
{anmeldung.email}
- - -
- {#if anmeldung.noteDeutsch} -
Deutsch: {anmeldung.noteDeutsch}
- {/if} - {#if anmeldung.noteMathe} -
Mathe: {anmeldung.noteMathe}
- {/if} + + + +
+
Deutsch: {anmeldung.noteDeutsch || '—'}
+
Mathe: {anmeldung.noteMathe || '—'}
{#if anmeldung.sozialverhalten} -
Sozialverhalten: {anmeldung.sozialverhalten}
+
+ Sozialverhalten:
+ {anmeldung.sozialverhalten} +
{/if}
- - -
+ + + +
{#if anmeldung.wunsch1}
- 1 - {anmeldung.wunsch1.name} + 1 + {anmeldung.wunsch1.name}
{/if} + {#if anmeldung.wunsch2}
- 2 - {anmeldung.wunsch2.name} + 2 + {anmeldung.wunsch2.name}
{/if} + {#if anmeldung.wunsch3}
- 3 - {anmeldung.wunsch3.name} + 3 + {anmeldung.wunsch3.name}
{/if}
- - - {#if anmeldung.pdfs.length > 0} -
- {#each anmeldung.pdfs as pdf, index} - - {/each} -
- {:else} - Keine Dokumente - {/if} + +
+ {/each} +
- + + {formatDate(anmeldung.timestamp)} - + + -
+
- + - + +
- -
-
- \ No newline at end of file diff --git a/src/routes/admin/dienststellen/+page.server.ts b/src/routes/admin/dienststellen/+page.server.ts index a39bc2d..2692b5b 100644 --- a/src/routes/admin/dienststellen/+page.server.ts +++ b/src/routes/admin/dienststellen/+page.server.ts @@ -1,8 +1,16 @@ +// src/routes/admin/dienstellen/+page.server.ts import type { PageServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; export const load: PageServerLoad = async ({ cookies }) => { - if (cookies.get('admin_session') !== 'true') { - throw redirect(303, '/admin'); // zurück zur Login-Seite + // Korrigiere Cookie-Name um konsistent zu sein + const adminAuth = cookies.get('admin-auth'); + + if (adminAuth !== 'authenticated') { + throw redirect(303, '/admin'); } + + return { + title: 'Dienstellen verwalten' + }; }; \ No newline at end of file diff --git a/src/routes/admin/zeitraeume/+page.server.ts b/src/routes/admin/zeitraeume/+page.server.ts index a39bc2d..21b5aec 100644 --- a/src/routes/admin/zeitraeume/+page.server.ts +++ b/src/routes/admin/zeitraeume/+page.server.ts @@ -1,8 +1,16 @@ +// src/routes/admin/zeitraeume/+page.server.ts import type { PageServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; export const load: PageServerLoad = async ({ cookies }) => { - if (cookies.get('admin_session') !== 'true') { - throw redirect(303, '/admin'); // zurück zur Login-Seite + // Korrigiere Cookie-Name um konsistent zu sein + const adminAuth = cookies.get('admin-auth'); + + if (adminAuth !== 'authenticated') { + throw redirect(303, '/admin'); } + + return { + title: 'Zetraeume verwalten' + }; }; \ No newline at end of file diff --git a/src/routes/api/admin/anmeldungen/+server.ts b/src/routes/api/admin/anmeldungen/+server.ts index e3789c0..5b8b09b 100644 --- a/src/routes/api/admin/anmeldungen/+server.ts +++ b/src/routes/api/admin/anmeldungen/+server.ts @@ -1,3 +1,4 @@ +// src/routes/api/admin/anmeldungen/+server.ts import { PrismaClient, Status } from '@prisma/client'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; @@ -8,31 +9,55 @@ const prisma = new PrismaClient(); import type { Cookies } from '@sveltejs/kit'; +// Korrigierte Auth-Funktion mit neuem Cookie-Namen function checkAuth(cookies: Cookies) { - return cookies.get('admin_session') === 'true'; + return cookies.get('admin-auth') === 'authenticated'; } export const GET: RequestHandler = async ({ cookies }) => { - if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); - const anmeldungen = await prisma.anmeldung.findMany({ - include: { - wunsch1: true, - wunsch2: true, - wunsch3: true, - pdfs: true - }, - orderBy: { timestamp: 'desc' } - }); - return new Response(JSON.stringify(anmeldungen), { - headers: { 'Content-Type': 'application/json' } - }); + if (!checkAuth(cookies)) { + return new Response( + JSON.stringify({ error: 'Nicht autorisiert' }), + { + status: 401, + headers: { 'Content-Type': 'application/json' } + } + ); + } + + try { + const anmeldungen = await prisma.anmeldung.findMany({ + include: { + wunsch1: true, + wunsch2: true, + wunsch3: true, + pdfs: true + }, + orderBy: { timestamp: 'desc' } + }); + + return new Response(JSON.stringify(anmeldungen), { + headers: { 'Content-Type': 'application/json' } + }); + } catch (error) { + console.error('Fehler beim Laden der Anmeldungen:', error); + return json({ error: 'Fehler beim Laden der Anmeldungen' }, { status: 500 }); + } }; -export const POST: RequestHandler = async ({ url }) => { +export const POST: RequestHandler = async ({ url, cookies, request }) => { + if (!checkAuth(cookies)) { + return json({ error: 'Nicht autorisiert' }, { status: 401 }); + } + const id = Number(url.searchParams.get('id')); if (!id) return json({ error: 'Ungültige ID' }, { status: 400 }); try { + // Prüfe ob eine spezifische Dienststelle zugewiesen werden soll + const body = await request.json().catch(() => ({})); + const dienststelleId = body.dienststelleId; + const anmeldung = await prisma.anmeldung.findUnique({ where: { id }, include: { @@ -42,8 +67,44 @@ export const POST: RequestHandler = async ({ url }) => { } }); - if (!anmeldung) return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 }); + if (!anmeldung) { + return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 }); + } + // Falls spezifische Dienststelle gewählt wurde + if (dienststelleId) { + const dienststelle = await prisma.dienststelle.findUnique({ + where: { id: dienststelleId } + }); + + if (!dienststelle) { + return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 }); + } + + if (dienststelle.plaetze <= 0) { + return json({ error: 'Keine verfügbaren Plätze bei dieser Dienststelle' }, { status: 409 }); + } + + await prisma.$transaction([ + prisma.anmeldung.update({ + where: { id }, + data: { + status: Status.ANGENOMMEN, + zugewiesenId: dienststelleId + } + }), + prisma.dienststelle.update({ + where: { id: dienststelleId }, + data: { + plaetze: { decrement: 1 } + } + }) + ]); + + return json({ success: true, message: `Zugewiesen an: ${dienststelle.name}` }); + } + + // Fallback: Automatische Zuweisung nach Wunschreihenfolge const wuensche = [anmeldung.wunsch1, anmeldung.wunsch2, anmeldung.wunsch3]; for (const wunsch of wuensche) { @@ -70,17 +131,51 @@ export const POST: RequestHandler = async ({ url }) => { return json({ error: 'Keine verfügbaren Plätze bei Wunsch-Dienststellen' }, { status: 409 }); } catch (err) { - console.error(err); + console.error('Fehler beim Annehmen der Anmeldung:', err); return json({ error: 'Interner Serverfehler' }, { status: 500 }); } -} +}; + +// Neue PATCH-Route für Ablehnung +export const PATCH: RequestHandler = async ({ url, cookies, request }) => { + if (!checkAuth(cookies)) { + return json({ error: 'Nicht autorisiert' }, { status: 401 }); + } + + const id = Number(url.searchParams.get('id')); + if (!id) return json({ error: 'Ungültige ID' }, { status: 400 }); + + try { + const body = await request.json().catch(() => ({})); + + if (body.action === 'reject') { + await prisma.anmeldung.update({ + where: { id }, + data: { + status: Status.ABGELEHNT + } + }); + + return json({ success: true, message: 'Anmeldung abgelehnt' }); + } + + return json({ error: 'Unbekannte Aktion' }, { status: 400 }); + } catch (err) { + console.error('Fehler beim Ablehnen der Anmeldung:', err); + return json({ error: 'Interner Serverfehler' }, { status: 500 }); + } +}; export const DELETE: RequestHandler = async ({ cookies, url }) => { - if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); + if (!checkAuth(cookies)) { + return json({ error: 'Nicht autorisiert' }, { status: 401 }); + } + const id = Number(url.searchParams.get('id')); if (isNaN(id)) { return json({ error: 'Ungültige ID' }, { status: 400 }); } + try { // 1. Alle PDF-Einträge zur Anmeldung laden const pdfs = await prisma.pdfDatei.findMany({ @@ -102,16 +197,18 @@ export const DELETE: RequestHandler = async ({ cookies, url }) => { } // 3. PDF-Datensätze aus DB löschen - await prisma.pdfDatei.deleteMany({ - where: {anmeldungId: id} - }); + await prisma.pdfDatei.deleteMany({ + where: { anmeldungId: id } + }); - // Anmeldung löschen - await prisma.anmeldung.delete({where: { id } }); - return json({ ok: true }); + // 4. Anmeldung löschen + await prisma.anmeldung.delete({ + where: { id } + }); + + return json({ success: true, message: 'Anmeldung erfolgreich gelöscht' }); } catch (error) { console.error('Fehler beim Löschen der Anmeldung:', error); return json({ error: 'Löschen fehlgeschlagen' }, { status: 500 }); } -}; - +}; \ No newline at end of file diff --git a/src/routes/api/admin/dienststellen/+server.ts b/src/routes/api/admin/dienststellen/+server.ts index c882558..167f161 100644 --- a/src/routes/api/admin/dienststellen/+server.ts +++ b/src/routes/api/admin/dienststellen/+server.ts @@ -1,3 +1,4 @@ +// src/routes/api/admin/dienststellen/+server.ts import { PrismaClient } from '@prisma/client'; import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; @@ -6,73 +7,187 @@ const prisma = new PrismaClient(); import type { Cookies } from '@sveltejs/kit'; +// Korrigierte Auth-Funktion mit neuem Cookie-Namen function checkAuth(cookies: Cookies) { - return cookies.get('admin_session') === 'true'; + return cookies.get('admin-auth') === 'authenticated'; } export const GET: RequestHandler = async ({ cookies }) => { - if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); - const dienststellen = await prisma.dienststelle.findMany({ orderBy: { name: 'asc' } }); - return json(dienststellen); + if (!checkAuth(cookies)) { + return new Response( + JSON.stringify({ error: 'Nicht autorisiert' }), + { + status: 401, + headers: { 'Content-Type': 'application/json' } + } + ); + } + + try { + const dienststellen = await prisma.dienststelle.findMany({ + orderBy: { name: 'asc' }, + /* + include: { + _count: { + select: { + Anmeldung: true // Use the correct relation name as defined in your Prisma schema + } + } + } + */ + }); + return json(dienststellen); + } catch (error) { + console.error('Fehler beim Laden der Dienststellen:', error); + return json({ error: 'Fehler beim Laden der Dienststellen' }, { status: 500 }); + } }; export const POST: RequestHandler = async ({ cookies, request }) => { - if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); - const { name, plaetze } = await request.json(); - if (typeof plaetze !== 'number' || plaetze < 0) { - return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 }); + if (!checkAuth(cookies)) { + return json({ error: 'Nicht autorisiert' }, { status: 401 }); } + try { - const created = await prisma.dienststelle.create({ data: { - name, - plaetze, - } }); - return json(created); - } catch (e) { - console.error('Fehler beim Hinzufuegen:', e); - return json({ error: 'Dienststelle existiert bereits' }, { status: 400 }); + const { name, plaetze } = await request.json(); + + // Validierung + if (!name || typeof name !== 'string' || name.trim().length === 0) { + return json({ error: 'Name ist erforderlich' }, { status: 400 }); + } + + if (typeof plaetze !== 'number' || plaetze < 0 || !Number.isInteger(plaetze)) { + return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 }); + } + + // Prüfe ob Name bereits existiert + const existing = await prisma.dienststelle.findFirst({ + where: { name: name.trim() } + }); + + if (existing) { + return json({ error: 'Eine Dienststelle mit diesem Namen existiert bereits' }, { status: 400 }); + } + + const created = await prisma.dienststelle.create({ + data: { + name: name.trim(), + plaetze, + } + }); + + return json(created, { status: 201 }); + } catch (error) { + console.error('Fehler beim Erstellen der Dienststelle:', error); + return json({ error: 'Fehler beim Erstellen der Dienststelle' }, { status: 500 }); } }; 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 }); + if (!checkAuth(cookies)) { + return json({ error: 'Nicht autorisiert' }, { status: 401 }); } try { + const { id, name, plaetze } = await request.json(); + + // Validierung + if (typeof id !== 'number' || isNaN(id)) { + return json({ error: 'Ungültige ID' }, { status: 400 }); + } + + if (!name || typeof name !== 'string' || name.trim().length === 0) { + return json({ error: 'Name ist erforderlich' }, { status: 400 }); + } + + if (typeof plaetze !== 'number' || plaetze < 0 || !Number.isInteger(plaetze)) { + return json({ error: 'Ungültige Anzahl an Plätzen' }, { status: 400 }); + } + + // Prüfe ob Dienststelle existiert + const existing = await prisma.dienststelle.findUnique({ where: { id } }); + if (!existing) { + return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 }); + } + + // Prüfe ob neuer Name bereits bei anderer Dienststelle existiert + const nameConflict = await prisma.dienststelle.findFirst({ + where: { + name: name.trim(), + NOT: { id }, + }, + }); + + if (nameConflict) { + return json({ error: 'Eine andere Dienststelle mit diesem Namen existiert bereits' }, { status: 400 }); + } + + // Prüfe ob Plätze reduziert werden und ob das möglich ist + const assignedCount = await prisma.anmeldung.count({ + where: { zugewiesenId: id } + }); + + if (plaetze < assignedCount) { + return json({ + error: `Plätze können nicht auf ${plaetze} reduziert werden. ${assignedCount} Anmeldungen sind bereits zugewiesen.` + }, { status: 400 }); + } + const updated = await prisma.dienststelle.update({ where: { id }, - data: { name, plaetze }, + data: { + name: name.trim(), + plaetze + }, }); + return json(updated); - } catch (e) { - console.error('Fehler beim Update:', e); - return json({ error: 'Update fehlgeschlagen' }, { status: 400 }); + } catch (error) { + console.error('Fehler beim Aktualisieren der Dienststelle:', error); + return json({ error: 'Fehler beim Aktualisieren der Dienststelle' }, { status: 500 }); } }; export const DELETE: RequestHandler = async ({ cookies, url }) => { - if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 }); + if (!checkAuth(cookies)) { + return json({ error: 'Nicht autorisiert' }, { status: 401 }); + } + const id = Number(url.searchParams.get('id')); - await prisma.dienststelle.delete({ where: { id } }); - return json({ success: true }); + + if (isNaN(id)) { + return json({ error: 'Ungültige ID' }, { status: 400 }); + } + + try { + // Prüfe ob Dienststelle existiert + const existing = await prisma.dienststelle.findUnique({ where: { id } }); + if (!existing) { + return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 }); + } + + // Prüfe ob noch Anmeldungen zugewiesen sind + const assignedCount = await prisma.anmeldung.count({ + where: { + OR: [ + { zugewiesenId: id }, + { wunsch1Id: id }, + { wunsch2Id: id }, + { wunsch3Id: id } + ] + } + }); + + if (assignedCount > 0) { + return json({ + error: 'Dienststelle kann nicht gelöscht werden. Es sind noch Anmeldungen damit verknüpft.' + }, { status: 400 }); + } + + await prisma.dienststelle.delete({ where: { id } }); + return json({ success: true, message: 'Dienststelle erfolgreich gelöscht' }); + } catch (error) { + console.error('Fehler beim Löschen der Dienststelle:', error); + return json({ error: 'Fehler beim Löschen der Dienststelle' }, { status: 500 }); + } }; \ No newline at end of file diff --git a/src/routes/api/admin/logout/+server.ts b/src/routes/api/admin/logout/+server.ts index b548e99..c6cc6df 100644 --- a/src/routes/api/admin/logout/+server.ts +++ b/src/routes/api/admin/logout/+server.ts @@ -1,6 +1,15 @@ +// src/routes/api/admin/logout/+server.ts import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ cookies }) => { - cookies.delete('admin_session', { path: '/' }); - return new Response('Ausgeloggt'); + // Cookie löschen mit korrektem Namen + cookies.delete('admin-auth', { path: '/' }); + + return new Response( + JSON.stringify({ success: true, message: 'Erfolgreich ausgeloggt' }), + { + status: 200, + headers: { 'Content-Type': 'application/json' } + } + ); }; \ No newline at end of file