pdf hochladen und Anmeldungen loeschen

This commit is contained in:
titver968
2025-04-24 16:41:56 +02:00
parent e3c8dff646
commit 063f7f433c
11 changed files with 247 additions and 52 deletions

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Anmeldung" ADD COLUMN "pdfdatei" TEXT;

View File

@@ -0,0 +1,39 @@
/*
Warnings:
- You are about to drop the column `pdfdatei` on the `Anmeldung` table. All the data in the column will be lost.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Anmeldung" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"anrede" TEXT NOT NULL,
"vorname" TEXT NOT NULL,
"nachname" TEXT NOT NULL,
"geburtsdatum" TEXT NOT NULL,
"strasse" TEXT NOT NULL,
"hausnummer" TEXT NOT NULL,
"ort" TEXT NOT NULL,
"plz" TEXT NOT NULL,
"telefon" TEXT NOT NULL,
"email" TEXT NOT NULL,
"schulart" TEXT NOT NULL,
"zeitraum" TEXT NOT NULL,
"motivation" TEXT NOT NULL,
"pdfDatei" TEXT,
"wunsch1Id" INTEGER NOT NULL,
"wunsch2Id" INTEGER NOT NULL,
"wunsch3Id" INTEGER NOT NULL,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Anmeldung_wunsch1Id_fkey" FOREIGN KEY ("wunsch1Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch2Id_fkey" FOREIGN KEY ("wunsch2Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch3Id_fkey" FOREIGN KEY ("wunsch3Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Anmeldung" ("anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "ort", "plz", "schulart", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zeitraum") SELECT "anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "ort", "plz", "schulart", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zeitraum" FROM "Anmeldung";
DROP TABLE "Anmeldung";
ALTER TABLE "new_Anmeldung" RENAME TO "Anmeldung";
CREATE UNIQUE INDEX "Anmeldung_email_key" ON "Anmeldung"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -0,0 +1,46 @@
/*
Warnings:
- You are about to drop the column `pdfDatei` on the `Anmeldung` table. All the data in the column will be lost.
*/
-- CreateTable
CREATE TABLE "PdfDatei" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"pfad" TEXT NOT NULL,
"anmeldungId" INTEGER NOT NULL,
CONSTRAINT "PdfDatei_anmeldungId_fkey" FOREIGN KEY ("anmeldungId") REFERENCES "Anmeldung" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Anmeldung" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"anrede" TEXT NOT NULL,
"vorname" TEXT NOT NULL,
"nachname" TEXT NOT NULL,
"geburtsdatum" TEXT NOT NULL,
"strasse" TEXT NOT NULL,
"hausnummer" TEXT NOT NULL,
"ort" TEXT NOT NULL,
"plz" TEXT NOT NULL,
"telefon" TEXT NOT NULL,
"email" TEXT NOT NULL,
"schulart" TEXT NOT NULL,
"zeitraum" TEXT NOT NULL,
"motivation" TEXT NOT NULL,
"wunsch1Id" INTEGER NOT NULL,
"wunsch2Id" INTEGER NOT NULL,
"wunsch3Id" INTEGER NOT NULL,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Anmeldung_wunsch1Id_fkey" FOREIGN KEY ("wunsch1Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch2Id_fkey" FOREIGN KEY ("wunsch2Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Anmeldung_wunsch3Id_fkey" FOREIGN KEY ("wunsch3Id") REFERENCES "Dienststelle" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Anmeldung" ("anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "ort", "plz", "schulart", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zeitraum") SELECT "anrede", "email", "geburtsdatum", "hausnummer", "id", "motivation", "nachname", "ort", "plz", "schulart", "strasse", "telefon", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zeitraum" FROM "Anmeldung";
DROP TABLE "Anmeldung";
ALTER TABLE "new_Anmeldung" RENAME TO "Anmeldung";
CREATE UNIQUE INDEX "Anmeldung_email_key" ON "Anmeldung"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

Binary file not shown.

View File

@@ -47,4 +47,13 @@ model Anmeldung {
wunsch3 Dienststelle @relation("Wunsch3", fields: [wunsch3Id], references: [id]) wunsch3 Dienststelle @relation("Wunsch3", fields: [wunsch3Id], references: [id])
timestamp DateTime @default(now()) timestamp DateTime @default(now())
pdfs PdfDatei[] @relation("AnmeldungPdfs")
} }
model PdfDatei {
id Int @id @default(autoincrement())
pfad String
anmeldung Anmeldung @relation("AnmeldungPdfs", fields: [anmeldungId], references: [id])
anmeldungId Int
}

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let anrede = ''; let anrede = '';
let vorname = ''; let vorname = '';
let nachname = ''; let nachname = '';
@@ -17,9 +18,10 @@
let wunsch1Id = ''; let wunsch1Id = '';
let wunsch2Id = ''; let wunsch2Id = '';
let wunsch3Id = ''; let wunsch3Id = '';
let pdfDateien: File[] = [];
let fehler = ''; let fehler = '';
let success = false; let success = false;
let dienststellen: any[]; let dienststellen: any[];
onMount(async () => { onMount(async () => {
@@ -28,16 +30,33 @@
}); });
async function anmelden() { async function anmelden() {
const data = new FormData();
data.append('anrede', anrede);
data.append('vorname', vorname);
data.append('nachname', nachname);
data.append('geburtsdatum', geburtsdatum);
data.append('strasse', strasse);
data.append('hausnummer', hausnummer);
data.append('ort', ort);
data.append('plz', plz);
data.append('telefon', telefon);
data.append('email', email);
data.append('schulart', schulart);
data.append('zeitraum', zeitraum);
data.append('motivation', motivation);
data.append('wunsch1Id', wunsch1Id);
data.append('wunsch2Id', wunsch2Id);
data.append('wunsch3Id', wunsch3Id);
for (const pdf of pdfDateien) {
data.append('pdfs', pdf);
}
const res = await fetch('/api/anmelden', { const res = await fetch('/api/anmelden', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, body: data
body: JSON.stringify({ });
anrede, vorname, nachname, geburtsdatum,
strasse, hausnummer, ort, plz,
telefon, email, schulart, zeitraum, motivation,
wunsch1Id, wunsch2Id, wunsch3Id
})
});
const result = await res.json(); const result = await res.json();
if (!res.ok) { if (!res.ok) {
@@ -108,6 +127,19 @@
class="w-full border border-gray-300 rounded-xl p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 h-32 resize-none" > class="w-full border border-gray-300 rounded-xl p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 h-32 resize-none" >
</textarea> </textarea>
<!-- Mehrere PDF Upload -->
<div>
<label for="pdf-upload" class="block text-gray-700 font-medium mb-1">PDFs hochladen (optional):</label>
<input
id="pdf-upload"
type="file"
accept="application/pdf"
multiple
on:change={(e) => pdfDateien = Array.from((e.target as HTMLInputElement).files || [])}
class="input"
/>
</div>
<!-- Button --> <!-- Button -->
<button type="submit" <button type="submit"
class="w-full bg-blue-600 text-white py-3 rounded-xl hover:bg-blue-700 transition-all"> class="w-full bg-blue-600 text-white py-3 rounded-xl hover:bg-blue-700 transition-all">

View File

@@ -1,4 +1,4 @@
import type { PageServerLoad } from '../../api/admin/anmeldungen/$types'; import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ cookies }) => { export const load: PageServerLoad = async ({ cookies }) => {

View File

@@ -1,6 +1,18 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let anmeldungen = []; interface Anmeldung {
anrede: string;
vorname: string;
nachname: string;
email: string;
wunsch1?: { name: string };
wunsch2?: { name: string };
wunsch3?: { name: string };
timestamp: number;
id: number;
}
let anmeldungen: Anmeldung[] = [];
async function ladeAnmeldungen() { async function ladeAnmeldungen() {
const res = await fetch('/api/admin/anmeldungen'); const res = await fetch('/api/admin/anmeldungen');
@@ -18,7 +30,7 @@
await ladeAnmeldungen(); await ladeAnmeldungen();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
alert('Fehler beim Löschen der Anmeldung.\n' + error.message); alert('Fehler beim Löschen der Anmeldung.\n' + (error as Error).message);
} }
} }
@@ -34,6 +46,7 @@
<th class="p-2 text-left">Name</th> <th class="p-2 text-left">Name</th>
<th class="p-2 text-left">E-Mail</th> <th class="p-2 text-left">E-Mail</th>
<th class="p-2 text-left">Wunsch 13</th> <th class="p-2 text-left">Wunsch 13</th>
<th class="p-2 text-left">Datum</th>
<th class="p-2 text-left">Dateien</th> <th class="p-2 text-left">Dateien</th>
<th class="p-2 text-left">Aktionen</th> <th class="p-2 text-left">Aktionen</th>
</tr> </tr>
@@ -48,6 +61,15 @@
{a.wunsch2?.name}<br> {a.wunsch2?.name}<br>
{a.wunsch3?.name} {a.wunsch3?.name}
</td> </td>
<td class="p-2">{new Date(a.timestamp).toLocaleDateString()}</td>
<td class="p-2">
{#each a.pdfs as pdf}
<li>
<a href={pdf.pfad} target="_blank" class="text-blue-600 hover:underline">
PDF ansehen
</a>
</li>
{/each}
</td> </td>
<td class="p-2 text-right"> <td class="p-2 text-right">
<button <button
@@ -71,7 +93,5 @@
</button> </button>
</div> </div>
<style> <style>
.input {
@apply border rounded px-3 py-2 w-full;
/* Removed unused .input selector */ /* Removed unused .input selector */

View File

@@ -1,9 +1,8 @@
//import { PrismaClient } from '@prisma/client';
//import type { RequestHandler } from '@sveltejs/kit';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import fs from 'fs/promises';
import path from 'path';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
@@ -17,7 +16,8 @@ export const GET: RequestHandler = async ({ cookies }) => {
include: { include: {
wunsch1: true, wunsch1: true,
wunsch2: true, wunsch2: true,
wunsch3: true wunsch3: true,
pdfs: true
}, },
orderBy: { timestamp: 'desc' } orderBy: { timestamp: 'desc' }
}); });
@@ -29,7 +29,36 @@ export const GET: RequestHandler = async ({ cookies }) => {
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'));
if (!id) return new Response('Ungültige ID', { status: 400 }); if (isNaN(id)) {
await prisma.anmeldung.delete({ where: { id } }); return json({ error: 'Ungültige ID' }, { status: 400 });
return json({ success: true }); }
try {
// 1. Alle PDF-Einträge zur Anmeldung laden
const pdfs = await prisma.pdfDatei.findMany({
where: { anmeldungId: id }
});
// 2. Dateien vom Dateisystem löschen
for (const pdf of pdfs) {
const filePath = path.resolve('static', pdf.pfad.replace(/^\/+/, ''));
try {
await fs.unlink(filePath);
} catch (err) {
console.warn(`Datei konnte nicht gelöscht werden: ${filePath}`, err.message);
// Fehler ignorieren, Datei evtl. manuell entfernt
}
}
// 3. PDF-Datensätze aus DB löschen
await prisma.pdfDatei.deleteMany({
where: {anmeldungId: id}
});
// Anmeldung löschen
await prisma.anmeldung.delete({where: { id } });
return json({ ok: true });
} catch (error) {
console.error('Fehler beim Löschen der Anmeldung:', error);
return json({ error: 'Löschen fehlgeschlagen' }, { status: 500 });
}
}; };

View File

@@ -1,43 +1,61 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { json, type RequestHandler } from '@sveltejs/kit'; import { writeFile } from 'fs/promises';
import { Prisma } from '@prisma/client'; import { randomUUID } from 'crypto';
import { json } from '@sveltejs/kit';
const prisma = new PrismaClient(); const prisma = new PrismaClient();
export const POST: RequestHandler = async ({ request }) => { export async function POST({ request }) {
const data = await request.json(); const formData = await request.formData();
const get = (key: string) => formData.get(key)?.toString() ?? '';
const pdfs = formData.getAll('pdfs') as File[];
const gespeichertePfade: string[] = [];
for (const pdf of pdfs) {
if (pdf.size > 0 && pdf.type === 'application/pdf') {
const buffer = Buffer.from(await pdf.arrayBuffer());
const dateiname = `${randomUUID()}.pdf`;
const pfad = `/uploads/${dateiname}`;
await writeFile(`static${pfad}`, buffer);
gespeichertePfade.push(pfad);
}
}
try { try {
await prisma.anmeldung.create({ await prisma.anmeldung.create({
data: { data: {
anrede: data.anrede, anrede: get('anrede'),
vorname: data.vorname, vorname: get('vorname'),
nachname: data.nachname, nachname: get('nachname'),
geburtsdatum: data.geburtsdatum, geburtsdatum: get('geburtsdatum'),
strasse: data.strasse, strasse: get('strasse'),
hausnummer: data.hausnummer, hausnummer: get('hausnummer'),
ort: data.ort, ort: get('ort'),
plz: data.plz, plz: get('plz'),
telefon: data.telefon, telefon: get('telefon'),
email: data.email, email: get('email'),
schulart: data.schulart, schulart: get('schulart'),
zeitraum: data.zeitraum, zeitraum: get('zeitraum'),
motivation: data.motivation, motivation: get('motivation'),
wunsch1Id: data.wunsch1Id, wunsch1Id: parseInt(get('wunsch1Id')),
wunsch2Id: data.wunsch2Id, wunsch2Id: parseInt(get('wunsch2Id')),
wunsch3Id: data.wunsch3Id wunsch3Id: parseInt(get('wunsch3Id')),
pdfs: {
create: gespeichertePfade.map((pfad) => ({ pfad }))
}
} }
}); });
return json({ success: true }); return json({ success: true });
} catch (error) { } catch (err: unknown) {
if (error instanceof Prisma.PrismaClientKnownRequestError) { if (err instanceof Error && (err as { code?: string }).code === 'P2002') {
if (error.code === 'P2002') { return json({ error: 'Diese E-Mail wurde bereits verwendet.' }, { status: 400 });
// Eindeutigkeit verletzt
return json({ error: 'Diese E-Mail-Adresse wurde bereits verwendet.' }, { status: 400 });
}
} }
console.error(error);
return json({ error: 'Ein Fehler ist aufgetreten.' }, { status: 500 }); console.error(err);
return json({ error: 'Fehler beim Speichern.' }, { status: 500 });
} }
}; }