import type { Result } from "@repo/rdx-utils"; import type { Application, Request, RequestHandler, Response } from "express"; import { Router } from "express"; import { z } from "zod/v4"; import { AccessItemParamsSchema, AccessSaveRequestSchema, AccessScopeParamsSchema, } from "../../../common/dto/request/access.request.dto"; import type { AccessSnapshot } from "../../application/access.repository"; import type { AccessUseCases } from "../../application/access.use-cases"; import { buildAccessResponse } from "../../application/access-response.builder"; import type { AccessError } from "../../domain/access/access-error"; function errorStatus(code: string) { if (code === "INVALID_REQUEST") { return 400; } return code.endsWith("NOT_FOUND") ? 404 : 409; } function sendError(response: Response, code: string, status: number) { response.status(status).json({ code, requestId: response.getHeader("x-request-id") }); } function visibleSnapshot(snapshot: AccessSnapshot, scope: string) { // Las adiciones locales se cargan para detectar colisiones, pero nunca se exponen en el catálogo global. return scope === "global" ? { ...snapshot, context: { ...snapshot.context, catalog: [] } } : snapshot; } function sendResult( response: Response, result: Result, scope: string, status: number ) { if (!result.isSuccess) { sendError(response, result.error.code, errorStatus(result.error.code)); return; } response.status(status).json(buildAccessResponse(visibleSnapshot(result.data, scope))); } function actorId(request: Request) { if (!("session" in request)) { return undefined; } const parsed = z.object({ userId: z.uuid() }).safeParse(request.session); return parsed.success ? parsed.data.userId : undefined; } export function registerAccessRoutes( app: Application, useCases: AccessUseCases, authenticate: RequestHandler, legacyResourceWrites = false ) { const router = Router(); router.get("/:scope", (request, response, next) => { const scope = AccessScopeParamsSchema.safeParse(request.params); if (!(scope.success && z.strictObject({}).safeParse(request.query).success)) { sendError(response, "INVALID_REQUEST", 400); return; } useCases .get(scope.data.scope) .then((result) => sendResult(response, result, scope.data.scope, 200)) .catch(next); }); for (const path of ["/:scope", "/:scope/:id/update"]) { const schema = path.endsWith("update") ? AccessItemParamsSchema : AccessScopeParamsSchema; router.post(path, (request, response, next) => { const params = schema.safeParse(request.params); const body = AccessSaveRequestSchema.safeParse(request.body); const actor = actorId(request); if (!(params.success && body.success && actor)) { sendError(response, "INVALID_REQUEST", 400); return; } if (!legacyResourceWrites && body.data.values.kind === "resource") { sendError(response, "RESOURCE_MOVED", 409); return; } const id = z.object({ id: z.uuid() }).safeParse(params.data); const identity = id.success ? id.data.id : undefined; useCases .save(params.data.scope, identity, body.data.values, actor) .then((result) => sendResult(response, result, params.data.scope, identity ? 200 : 201)) .catch(next); }); } app.use("/api/access", authenticate, router); }