/** Loopback REST handlers for private retrieval trace management. */ import type { Collection } from "../../config/types"; import type { RetrievalTraceExportRequest, RetrievalTraceLabelRequest, } from "../../core/retrieval-trace-management"; import type { StoreError, StorePort, StoreResult } from "../../store/types"; import { authorizeCurrentEgress } from "../../core/egress-authorization"; import { RetrievalTraceManagementService } from "../../core/retrieval-trace-management"; const errorStatus = (error: StoreError): number => { if (error.code === "NOT_FOUND") return 404; if (error.code === "CONSTRAINT_VIOLATION") return 409; if (error.code === "INVALID_INPUT") return 400; if (error.code === "EGRESS_DENIED") return 403; return 500; }; const response = (result: StoreResult): Response => { if (result.ok) return Response.json(result.value); return Response.json( { error: { code: result.error.code, message: result.error.message, }, }, { status: errorStatus(result.error) } ); }; const parseJsonObject = async ( request: Request ): Promise | Response> => { try { const value = (await request.json()) as unknown; if (!value || typeof value !== "object" || Array.isArray(value)) { return Response.json( { error: { code: "INVALID_INPUT", message: "Request body must be a JSON object", }, }, { status: 400 } ); } return value as Record; } catch { return Response.json( { error: { code: "INVALID_INPUT", message: "Request body must be valid JSON", }, }, { status: 400 } ); } }; const parseOptionalInteger = ( value: string | null ): number | undefined | null => { if (value === null) return undefined; if (!/^\d+$/.test(value)) return null; const parsed = Number(value); return Number.isSafeInteger(parsed) ? parsed : null; }; export const handleTraceList = async ( store: StorePort, request: Request ): Promise => { const url = new URL(request.url); const limit = parseOptionalInteger(url.searchParams.get("limit")); if (limit === null) { return Response.json( { error: { code: "INVALID_INPUT", message: "limit must be a positive integer", }, }, { status: 400 } ); } return response( await new RetrievalTraceManagementService(store).list({ limit, cursor: url.searchParams.get("cursor") ?? undefined, }) ); }; export const handleTraceShow = async ( store: StorePort, traceId: string, request: Request ): Promise => { const detailLimit = parseOptionalInteger( new URL(request.url).searchParams.get("detailLimit") ); if (detailLimit === null) { return Response.json( { error: { code: "INVALID_INPUT", message: "detailLimit must be a positive integer", }, }, { status: 400 } ); } return response( await new RetrievalTraceManagementService(store).show(traceId, { detailLimit, }) ); }; export const handleTraceLabel = async ( store: StorePort, traceId: string, request: Request ): Promise => { const body = await parseJsonObject(request); if (body instanceof Response) return body; return response( await new RetrievalTraceManagementService(store).label({ ...body, traceId, } as unknown as RetrievalTraceLabelRequest) ); }; export const handleTraceExport = async ( store: StorePort, request: Request, collections: readonly Collection[] = [] ): Promise => { const body = await parseJsonObject(request); if (body instanceof Response) return body; return response( await new RetrievalTraceManagementService(store, { authorizeExport: async (lineage) => { const hostname = new URL(request.url).hostname; const destinationZone = ["localhost", "127.0.0.1", "::1"].includes( hostname ) ? "local_process" : "remote"; return authorizeCurrentEgress({ store, config: { collections }, lineage, action: "export", destinationZone, caller: { authenticated: true, operationAuthorized: true, }, contentClass: "retrieval_trace", }); }, }).export(body as unknown as RetrievalTraceExportRequest) ); }; export const handleTraceDelete = async ( store: StorePort, traceId: string ): Promise => response(await new RetrievalTraceManagementService(store).delete(traceId)); export const handleTracePurge = async (store: StorePort): Promise => response(await new RetrievalTraceManagementService(store).purge());