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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
87 lines
3.1 KiB
TypeScript
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
|
|
}
|