import { type Application, type RequestHandler, type Response, Router } from "express"; import { z } from "zod/v4"; import { ProcessTemplateInputSchema, StartProcessInputSchema, UpdateProcessInputSchema, } from "../../../common/processes"; import type { ProcessUseCases } from "../../application/process.use-cases"; import { buildProcess, buildProcessTemplate } from "../../application/process-response.builder"; const companyParams = z.strictObject({ companyId: z.uuid().transform((v) => v.toLowerCase()) }); const itemParams = companyParams.extend({ id: z.uuid().transform((v) => v.toLowerCase()) }); const templateParams = z.strictObject({ id: z.uuid().transform((v) => v.toLowerCase()) }); function fail(response: Response, code: string) { response .status(code === "INVALID_REQUEST" ? 400 : code.endsWith("NOT_FOUND") ? 404 : 409) .json({ code, requestId: response.getHeader("x-request-id") }); } function actorOf(request: unknown) { return z.object({ session: z.object({ userId: z.uuid() }) }).safeParse(request); } export function registerProcessRoutes( app: Application, useCases: ProcessUseCases, authenticate: RequestHandler ) { const router = Router(); router.use((request, response, next) => { if (!z.strictObject({}).safeParse(request.query).success) { fail(response, "INVALID_REQUEST"); return; } next(); }); router.get("/templates", (_request, response, next) => { useCases .catalog() .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } response.json({ templates: result.data.templates.map((t) => buildProcessTemplate(t, result.data)), }); }) .catch(next); }); for (const path of ["/templates", "/templates/:id/update"]) { router.post(path, (request, response, next) => { const body = z.strictObject({ values: ProcessTemplateInputSchema }).safeParse(request.body); const params = path.endsWith("update") ? templateParams.safeParse(request.params) : z.strictObject({}).safeParse(request.params); const actor = actorOf(request); if (!(body.success && params.success && actor.success)) { fail(response, "INVALID_REQUEST"); return; } const id = "id" in params.data && typeof params.data.id === "string" ? params.data.id : undefined; useCases .saveTemplate(body.data.values, actor.data.session.userId, id) .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } response .status(id ? 200 : 201) .json({ template: buildProcessTemplate(result.data.template, result.data.context) }); }) .catch(next); }); } router.get("/:companyId", (request, response, next) => { const params = companyParams.safeParse(request.params); if (!params.success) { fail(response, "INVALID_REQUEST"); return; } useCases .get(params.data.companyId) .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } response.json({ processes: result.data.processes.map((p) => buildProcess(p, result.data)), }); }) .catch(next); }); router.get("/:companyId/people/:id/review", (request, response, next) => { const params = itemParams.safeParse(request.params); if (!params.success) { fail(response, "INVALID_REQUEST"); return; } useCases .get(params.data.companyId) .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } const review = result.data.reviews.get(params.data.id); if (!review) { fail(response, "PROCESS_PERSON_NOT_FOUND"); return; } response.json({ review }); }) .catch(next); }); router.post("/:companyId", (request, response, next) => { const params = companyParams.safeParse(request.params); const body = z.strictObject({ values: StartProcessInputSchema }).safeParse(request.body); const actor = actorOf(request); if (!(params.success && body.success && actor.success)) { fail(response, "INVALID_REQUEST"); return; } useCases .start(params.data.companyId, body.data.values, actor.data.session.userId) .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } response .status(201) .json({ process: buildProcess(result.data.process, result.data.context) }); }) .catch(next); }); router.post("/:companyId/:id/update", (request, response, next) => { const params = itemParams.safeParse(request.params); const body = z.strictObject({ values: UpdateProcessInputSchema }).safeParse(request.body); const actor = actorOf(request); if (!(params.success && body.success && actor.success)) { fail(response, "INVALID_REQUEST"); return; } useCases .update(params.data.companyId, params.data.id, body.data.values, actor.data.session.userId) .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } response.json({ process: buildProcess(result.data.process, result.data.context) }); }) .catch(next); }); router.post("/:companyId/:id/refresh", (request, response, next) => { const params = itemParams.safeParse(request.params); const body = z.strictObject({ revisionId: z.uuid() }).safeParse(request.body); const actor = actorOf(request); if (!(params.success && body.success && actor.success)) { fail(response, "INVALID_REQUEST"); return; } useCases .refresh( params.data.companyId, params.data.id, body.data.revisionId, actor.data.session.userId ) .then((result) => { if (!result.isSuccess) { fail(response, result.error.code); return; } response.json({ process: buildProcess(result.data.process, result.data.context) }); }) .catch(next); }); app.use("/api/processes", authenticate, router); }