diff --git a/src/app/api/documents/[id]/route.ts b/src/app/api/documents/[id]/route.ts new file mode 100644 index 0000000..9ea005b --- /dev/null +++ b/src/app/api/documents/[id]/route.ts @@ -0,0 +1,34 @@ +// DELETE /api/documents/:id — delete a document and its stored file + +import { type NextRequest } from 'next/server'; +import { deleteDocument } from '@/lib/documents'; +import { logAuditEvent } from '@/lib/auth/audit'; +import { requirePermission } from '@/lib/auth/rbac'; + +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> }, +) { + const auth = await requirePermission('cases:edit'); + if ('response' in auth) return auth.response; + const { ctx } = auth; + + const { id } = await params; + + const deleted = await deleteDocument(ctx.tenantId, id); + + if (!deleted) { + return Response.json( + { error: 'Dokument nicht gefunden.' }, + { status: 404 }, + ); + } + + const ip = + request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? + request.headers.get('x-real-ip') ?? + undefined; + await logAuditEvent(ctx, 'delete', 'document', id, { filename: deleted.filename }, ip); + + return Response.json({ deleted: true }); +} diff --git a/src/components/documents/dokument-upload.tsx b/src/components/documents/dokument-upload.tsx index 44299f5..717d398 100644 --- a/src/components/documents/dokument-upload.tsx +++ b/src/components/documents/dokument-upload.tsx @@ -49,6 +49,7 @@ export default function DokumentUpload({ label = 'Dokument hochladen', }: DokumentUploadProps) { const [uploading, setUploading] = useState(false); + const [deleting, setDeleting] = useState(null); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); const [documents, setDocuments] = useState([]); @@ -109,6 +110,28 @@ export default function DokumentUpload({ } } + async function handleDelete(docId: string, filename: string) { + if (!confirm(`"${filename}" wirklich loeschen?`)) return; + + setError(''); + setSuccess(''); + setDeleting(docId); + + try { + const res = await fetch(`/api/documents/${docId}`, { method: 'DELETE' }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || 'Loeschen fehlgeschlagen'); + } + setSuccess(`"${filename}" wurde geloescht.`); + fetchDocuments(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); + } finally { + setDeleting(null); + } + } + function handleSubmit(e: React.FormEvent) { e.preventDefault(); const file = fileRef.current?.files?.[0]; @@ -196,6 +219,15 @@ export default function DokumentUpload({ > {STATUS_LABELS[doc.status] ?? doc.status} + ))} diff --git a/src/lib/documents/index.ts b/src/lib/documents/index.ts index aee2e3e..6872631 100644 --- a/src/lib/documents/index.ts +++ b/src/lib/documents/index.ts @@ -222,3 +222,26 @@ export async function getDocument(tenantId: string, documentId: string) { return doc ?? null; }); } + +/** + * Delete a document by ID. Removes the DB record and the stored file from disk. + * Returns the deleted document row, or null if not found. + */ +export async function deleteDocument(tenantId: string, documentId: string) { + const deleted = await withTenantDb(tenantId, async (tdb) => { + const [row] = await tdb + .delete(documents) + .where(eq(documents.id, documentId)) + .returning(); + return row ?? null; + }); + + if (deleted?.storagePath) { + const fs = await import('node:fs/promises'); + await fs.unlink(deleted.storagePath).catch(() => { + // File may already be removed — ignore cleanup errors + }); + } + + return deleted; +}