- /imports: Upload per Drag & Drop/Dateiauswahl mit Fortschritt, Liste mit Status, Neu verarbeiten
- /imports/[id]: Originaldokument + Prüfmaske (Kunde, Objekt, Ansprechpartner, Auftrag, Positionen),
unsichere Felder markiert, Kunden-/Objektentscheidung, Bestätigen/Verwerfen
- Datei-Route für die Vorschau, Server Actions (moduleGuard("imports"))
- API: POST /api/v1/work-orders/import, GET /api/v1/imports/[id], POST /api/v1/imports/[id]/confirm
- Texte messages/{de,en}/imports.json, Bericht docs/craftvia/lanes/import.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 lines
2.0 KiB
TypeScript
42 lines
2.0 KiB
TypeScript
import { moduleGuard } from "@/server/action-guard";
|
|
import { storage } from "@/server/storage/adapter";
|
|
import { ctxFromGuard } from "@/server/services/context";
|
|
import { getImportFile } from "@/server/services/imports/queries";
|
|
|
|
/**
|
|
* GET /imports/[id]/file — original document of an import for the review mask preview.
|
|
* Authorisation: session + DB-authoritative permission import:write + module + document
|
|
* visibility (getImportFile). Unknown/foreign ids → 404. `?download=1` forces a download.
|
|
* Inline delivery only for the allowlisted types stored by the import (PDF/JPEG/PNG) and
|
|
* only framable by the own origin.
|
|
*/
|
|
const INLINE = new Set(["application/pdf", "image/jpeg", "image/png"]);
|
|
|
|
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
let file: { storageKey: string; mimeType: string; fileName: string };
|
|
try {
|
|
const ctx = ctxFromGuard(await moduleGuard("imports")("import:write"));
|
|
const { id } = await params;
|
|
file = await getImportFile(ctx, id);
|
|
} catch {
|
|
return new Response("Nicht gefunden.", { status: 404 });
|
|
}
|
|
|
|
const content = await storage.get(file.storageKey);
|
|
if (!content) return new Response("Datei nicht verfügbar.", { status: 404 });
|
|
|
|
const download = new URL(req.url).searchParams.get("download") === "1";
|
|
const inline = !download && INLINE.has(file.mimeType);
|
|
const asciiName = file.fileName.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
|
|
const headers = new Headers({
|
|
"Content-Type": INLINE.has(file.mimeType) ? file.mimeType : "application/octet-stream",
|
|
"Content-Disposition": `${inline ? "inline" : "attachment"}; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(file.fileName)}`,
|
|
"X-Content-Type-Options": "nosniff",
|
|
"Cache-Control": "private, no-store",
|
|
"Content-Security-Policy": "frame-ancestors 'self'",
|
|
"X-Frame-Options": "SAMEORIGIN",
|
|
});
|
|
if (content.size != null) headers.set("Content-Length", String(content.size));
|
|
return new Response(content.stream, { headers });
|
|
}
|