Files
AfiS-Aktensystem/web/app/api/files/route.ts
T
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

87 lines
3.1 KiB
TypeScript

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<Response> {
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
}