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])
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">
import { onMount } from 'svelte';
let anrede = '';
let vorname = '';
let nachname = '';
@@ -17,9 +18,10 @@
let wunsch1Id = '';
let wunsch2Id = '';
let wunsch3Id = '';
let pdfDateien: File[] = [];
let fehler = '';
let success = false;
let dienststellen: any[];
onMount(async () => {
@@ -28,16 +30,33 @@
});
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', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
anrede, vorname, nachname, geburtsdatum,
strasse, hausnummer, ort, plz,
telefon, email, schulart, zeitraum, motivation,
wunsch1Id, wunsch2Id, wunsch3Id
})
});
body: data
});
const result = await res.json();
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" >
</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 type="submit"
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';
export const load: PageServerLoad = async ({ cookies }) => {

View File

@@ -1,6 +1,18 @@
<script lang="ts">
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() {
const res = await fetch('/api/admin/anmeldungen');
@@ -18,7 +30,7 @@
await ladeAnmeldungen();
} catch (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">E-Mail</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">Aktionen</th>
</tr>
@@ -48,6 +61,15 @@
{a.wunsch2?.name}<br>
{a.wunsch3?.name}
</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 class="p-2 text-right">
<button
@@ -71,7 +93,5 @@
</button>
</div>
<style>
.input {
@apply border rounded px-3 py-2 w-full;
<style>
/* 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 { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import fs from 'fs/promises';
import path from 'path';
const prisma = new PrismaClient();
@@ -17,7 +16,8 @@ export const GET: RequestHandler = async ({ cookies }) => {
include: {
wunsch1: true,
wunsch2: true,
wunsch3: true
wunsch3: true,
pdfs: true
},
orderBy: { timestamp: 'desc' }
});
@@ -29,7 +29,36 @@ export const GET: RequestHandler = async ({ cookies }) => {
export const DELETE: RequestHandler = async ({ cookies, url }) => {
if (!checkAuth(cookies)) return new Response('Nicht erlaubt', { status: 401 });
const id = Number(url.searchParams.get('id'));
if (!id) return new Response('Ungültige ID', { status: 400 });
await prisma.anmeldung.delete({ where: { id } });
return json({ success: true });
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({
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 { json, type RequestHandler } from '@sveltejs/kit';
import { Prisma } from '@prisma/client';
import { writeFile } from 'fs/promises';
import { randomUUID } from 'crypto';
import { json } from '@sveltejs/kit';
const prisma = new PrismaClient();
export const POST: RequestHandler = async ({ request }) => {
const data = await request.json();
export async function POST({ request }) {
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 {
await prisma.anmeldung.create({
data: {
anrede: data.anrede,
vorname: data.vorname,
nachname: data.nachname,
geburtsdatum: data.geburtsdatum,
strasse: data.strasse,
hausnummer: data.hausnummer,
ort: data.ort,
plz: data.plz,
telefon: data.telefon,
email: data.email,
schulart: data.schulart,
zeitraum: data.zeitraum,
motivation: data.motivation,
wunsch1Id: data.wunsch1Id,
wunsch2Id: data.wunsch2Id,
wunsch3Id: data.wunsch3Id
anrede: get('anrede'),
vorname: get('vorname'),
nachname: get('nachname'),
geburtsdatum: get('geburtsdatum'),
strasse: get('strasse'),
hausnummer: get('hausnummer'),
ort: get('ort'),
plz: get('plz'),
telefon: get('telefon'),
email: get('email'),
schulart: get('schulart'),
zeitraum: get('zeitraum'),
motivation: get('motivation'),
wunsch1Id: parseInt(get('wunsch1Id')),
wunsch2Id: parseInt(get('wunsch2Id')),
wunsch3Id: parseInt(get('wunsch3Id')),
pdfs: {
create: gespeichertePfade.map((pfad) => ({ pfad }))
}
}
});
return json({ success: true });
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
// Eindeutigkeit verletzt
return json({ error: 'Diese E-Mail-Adresse wurde bereits verwendet.' }, { status: 400 });
}
} catch (err: unknown) {
if (err instanceof Error && (err as { code?: string }).code === 'P2002') {
return json({ error: 'Diese E-Mail 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 });
}
};
}