import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import { z } from "zod"; import { isPrincipalContext, requirePermission } from "../auth/authorization.js"; import type { CatalogOperationCoordinator } from "../catalog/operation-coordinator.js"; import { CatalogOperationInProgressError, CatalogUnavailableError, type CatalogRepository, } from "../catalog/types.js"; const idSchema = z.uuid(); const consolidationSchema = z.discriminatedUnion("target", [ z.object({ target: z.enum(["tables", "columns"]), targetIds: z.array(idSchema).min(1).max(10_000), }).strict(), z.object({ target: z.enum(["database", "database_columns"]) }).strict(), ]); function manage(request: FastifyRequest, reply: FastifyReply) { return isPrincipalContext(requirePermission(request, reply, "database.manage")); } function safeError(reply: FastifyReply, error: unknown) { if (error instanceof CatalogUnavailableError) { return reply.code(503).send({ code: "catalog_unavailable", message: "Database catalog is unavailable." }); } if (error instanceof CatalogOperationInProgressError) { return reply.code(409).send({ code: "database_operation_in_progress", message: "A database operation is already in progress.", }); } if (error instanceof z.ZodError) { return reply.code(400).send({ code: "description_consolidation_invalid", message: "Description consolidation request is invalid.", }); } return reply.code(500).send({ code: "description_consolidation_failed", message: "Description consolidation failed.", }); } export function catalogDescriptionConsolidationRoutes( app: FastifyInstance, deps: { repository: CatalogRepository; operations: CatalogOperationCoordinator }, ): void { app.post("/catalog/databases/:databaseId/descriptions/consolidate", async (request, reply) => { if (!manage(request, reply)) return reply; try { const databaseId = idSchema.parse((request.params as { databaseId?: unknown }).databaseId); const input = consolidationSchema.parse(request.body); const targetIds = "targetIds" in input ? [...new Set(input.targetIds)] : []; const result = await deps.operations.run( databaseId, async () => await deps.repository.consolidateGeneratedDescriptions( databaseId, input.target, targetIds, ), ); if (!result) { return reply.code(404).send({ code: "catalog_target_not_found", message: "The database or one or more selected catalog targets were not found.", }); } return result; } catch (error) { return safeError(reply, error); } }); }