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.
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { createReadStream } from 'node:fs'
|
|
import { stat } from 'node:fs/promises'
|
|
import { Readable } from 'node:stream'
|
|
import { NextResponse } from 'next/server'
|
|
import { db } from '@/lib/db'
|
|
import { getCurrentUser } from '@/lib/auth/current'
|
|
import { recordRead } from '@/lib/audit/write'
|
|
import { absolutePath } from '@/lib/files/paths'
|
|
|
|
export const runtime = 'nodejs'
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
/**
|
|
* Herunterladen eines fertigen Exports.
|
|
*
|
|
* Nur der Auftraggeber selbst. Ein Export ist auf **seine** Stufe gekürzt und
|
|
* trägt **seinen** Namen im Wasserzeichen - ihn an jemand anderen auszuliefern
|
|
* hiesse, ein Dokument mit fremdem Namen zu verteilen.
|
|
*/
|
|
export async function GET(
|
|
_request: Request,
|
|
context: { params: Promise<{ id: string }> },
|
|
): Promise<Response> {
|
|
const user = await getCurrentUser()
|
|
if (!user) return new NextResponse(null, { status: 404 })
|
|
|
|
const { id } = await context.params
|
|
const auftrag = await db.exportJob.findFirst({
|
|
where: { id, requestedById: user.id, status: 'READY' },
|
|
select: { id: true, kind: true, targetId: true, storageKey: true },
|
|
})
|
|
if (!auftrag?.storageKey) return new NextResponse(null, { status: 404 })
|
|
|
|
let groesse: number
|
|
try {
|
|
groesse = (await stat(absolutePath(auftrag.storageKey))).size
|
|
} catch {
|
|
return new NextResponse(null, { status: 404 })
|
|
}
|
|
|
|
// Jeder Abruf wird protokolliert, nicht nur die Erzeugung: ein PDF kann
|
|
// mehrfach heruntergeladen und weitergegeben werden.
|
|
recordRead({
|
|
actorId: user.id,
|
|
action: 'export.download',
|
|
targetType: 'ExportJob',
|
|
targetId: auftrag.id,
|
|
clearanceAtTime: user.permissions.clearanceLevel,
|
|
meta: { kind: auftrag.kind, targetId: auftrag.targetId ?? undefined },
|
|
})
|
|
|
|
const stream = Readable.toWeb(createReadStream(absolutePath(auftrag.storageKey))) as ReadableStream
|
|
return new NextResponse(stream, {
|
|
headers: {
|
|
'Content-Type': 'application/pdf',
|
|
'Content-Length': String(groesse),
|
|
'X-Content-Type-Options': 'nosniff',
|
|
'Cache-Control': 'private, no-store',
|
|
'Content-Security-Policy': "default-src 'none'; sandbox",
|
|
'Content-Disposition': `attachment; filename="afis-export-${auftrag.id}.pdf"`,
|
|
},
|
|
})
|
|
}
|