import { NextResponse } from 'next/server' import { getCurrentUser } from '@/lib/auth/current' import { AccessError } from '@/lib/errors' import { acceptUpload, type AttachmentOwner } from '@/lib/files/service' import { runCheckChain } from '@/lib/files/process' import { MAX_FILE_BYTES } from '@/lib/files/limits' import { de } from '@/lib/i18n/de' export const runtime = 'nodejs' export const dynamic = 'force-dynamic' /** * Upload. * * Ein Route Handler und keine Server Action: nur so bekommt der Browser einen * Fortschrittsbalken, und 25 MB sind mehr, als eine Server Action annimmt. * * Die Antwort kommt, sobald die Datei in der Quarantäne liegt. Die Prüfkette * läuft danach weiter und meldet sich über `/api/events` — niemand wartet mit * laufendem Balken auf einen Virenscanner. Siehe docs/09. */ export async function POST(request: Request): Promise { const user = await getCurrentUser() if (!user) return NextResponse.json({ error: 'Nicht angemeldet' }, { status: 401 }) const laenge = Number(request.headers.get('content-length') ?? 0) if (laenge > MAX_FILE_BYTES * 1.1) { return NextResponse.json({ error: de.attachment.errors['file-too-large'] }, { status: 413 }) } let form: FormData try { form = await request.formData() } catch { return NextResponse.json({ error: de.actionErrors.default }, { status: 400 }) } const datei = form.get('file') if (!(datei instanceof File)) { return NextResponse.json({ error: de.actionErrors.default }, { status: 400 }) } const owner = ownerAusForm(form) if (!owner) return NextResponse.json({ error: de.actionErrors.default }, { status: 400 }) try { const angelegt = await acceptUpload(user.permissions, { owner, fileName: datei.name || 'unbenannt', declaredType: datei.type || null, content: new Uint8Array(await datei.arrayBuffer()), }) // Bewusst nicht abgewartet: die Antwort soll sofort raus, damit die // Oberfläche den Anhang mit "Wird geprüft" zeigen kann. void runCheckChain(angelegt.id).catch((error: unknown) => { console.error('[dateien] Prüfkette abgebrochen', error) }) return NextResponse.json({ id: angelegt.id }, { status: 201 }) } catch (error) { if (error instanceof AccessError) { const status = error.code === 'rate-limited' ? 429 : error.code === 'not-found' ? 404 : 400 return NextResponse.json( { error: de.attachment.errors[error.code] ?? de.actionErrors[error.code] ?? de.actionErrors.default }, { status }, ) } console.error('[dateien] Upload fehlgeschlagen', error) return NextResponse.json({ error: de.actionErrors.default }, { status: 500 }) } } function ownerAusForm(form: FormData): AttachmentOwner | null { const caseNumber = form.get('caseNumber') if (typeof caseNumber === 'string' && caseNumber) return { kind: 'case', caseNumber } const entryId = form.get('entryId') if (typeof entryId === 'string' && entryId) return { kind: 'entry', entryId } const documentNumber = form.get('documentNumber') if (typeof documentNumber === 'string' && documentNumber) { return { kind: 'document', documentNumber } } return null }