import { ZodError } from "zod"; import { moduleGuard } from "@/server/action-guard"; import { ModuleDisabledError } from "@/server/modules"; import { ForbiddenError, type Permission } from "@/server/rbac"; import { ctxFromGuard, ServiceError, type ServiceCtx } from "@/server/services/context"; /** * Thin adapter for /api/v1 report route handlers: module gate + DB-authoritative permissions → ServiceCtx, * uniform JSON error mapping. (No shared requireApiContext exists yet; replace at merge if the architect adds one.) */ const guard = moduleGuard("reports"); const STATUS: Record = { not_found: 404, forbidden: 403, invalid: 400, conflict: 409, blocked: 422 }; export function apiError(err: unknown): Response { if (err instanceof ServiceError) return Response.json({ error: err.code, details: err.details ?? null }, { status: STATUS[err.code] }); if (err instanceof ZodError) return Response.json({ error: "invalid", details: err.issues.map((i) => ({ path: i.path.join("."), code: i.code })) }, { status: 400 }); if (err instanceof ForbiddenError || err instanceof ModuleDisabledError) return Response.json({ error: "forbidden" }, { status: 403 }); if (err instanceof Error && /Nicht angemeldet|nicht aktiv|nicht mehr gueltig|Passwortwechsel/.test(err.message)) { return Response.json({ error: "unauthorized" }, { status: 401 }); } console.error("[api/reports]", err); return Response.json({ error: "internal" }, { status: 500 }); } export async function withReportsApi(permissions: Permission[], handler: (ctx: ServiceCtx) => Promise): Promise { try { const g = await guard(...permissions); return await handler(ctxFromGuard(g)); } catch (err) { return apiError(err); } } export async function readJson(req: Request): Promise> { const text = await req.text(); if (!text.trim()) return {}; try { const v = JSON.parse(text); return v && typeof v === "object" && !Array.isArray(v) ? v : {}; } catch { throw new ServiceError("invalid", "body must be JSON"); } } export function reportDto(r: { id: string; type: string; status: string; version: number; workOrderId: string; lineageId: string; pdfDocumentId?: string | null }) { return { id: r.id, type: r.type, status: r.status, version: r.version, workOrderId: r.workOrderId, lineageId: r.lineageId, hasPdf: Boolean(r.pdfDocumentId) }; }