/** * TASK-0124 — Database AI endpoints (route style mirrors `ai-writing.controller.ts`). * * POST /workspaces/:workspaceId/ai/databases/generate (SSE) * POST /workspaces/:workspaceId/ai/databases/extract (SSE) * POST /workspaces/:workspaceId/ai/databases/extract/preview (JSON, no write) * POST /workspaces/:workspaceId/ai/databases/formulas (JSON) * * Pre-stream failures (permission 403, rate 429, budget 402, validation 400, * 413 oversized text) are a canonical JSON envelope (thrown before the * `text/event-stream` headers). In-stream failures (invalid structure after * the retry-once, provider outage) are in-band `error` + terminal * `done {reason:"error"}` frames. */ import { Body, Controller, HttpCode, Inject, Param, Post, Req, Res } from "@nestjs/common"; import type { Request, Response } from "express"; import { DatabaseAiService } from "./database-ai.service.js"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { requireActor } from "../../common/types.js"; import { AiExtractDto, AiExtractPreviewDto, AiGenerateDatabaseDto, AiGenerateFormulaDto, type AiExtractInput, type AiExtractPreviewInput, type AiGenerateDatabaseInput, type AiGenerateFormulaInput, } from "./database-ai.dto.js"; @Controller() export class DatabaseAiController { constructor(@Inject(DatabaseAiService) private readonly dbAi: DatabaseAiService) {} /** Generate a database from a prompt (SSE). */ @Post("workspaces/:workspaceId/ai/databases/generate") generate( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(AiGenerateDatabaseDto)) body: AiGenerateDatabaseInput, @Req() req: Request, @Res() res: Response, ) { return this.dbAi.generate(workspaceId, requireActor(req), body, req, res); } /** Extract records from pasted text into an existing database (SSE). */ @Post("workspaces/:workspaceId/ai/databases/extract") extract( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(AiExtractDto)) body: AiExtractInput, @Req() req: Request, @Res() res: Response, ) { return this.dbAi.extract(workspaceId, requireActor(req), body, req, res); } /** Extract mapping dry-run (JSON, no write, no LLM). */ @Post("workspaces/:workspaceId/ai/databases/extract/preview") @HttpCode(200) preview( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(AiExtractPreviewDto)) body: AiExtractPreviewInput, @Req() req: Request, ) { return this.dbAi.preview(workspaceId, requireActor(req), body); } /** Generate a formula from a description (JSON, non-streamed). */ @Post("workspaces/:workspaceId/ai/databases/formulas") @HttpCode(200) formula( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(AiGenerateFormulaDto)) body: AiGenerateFormulaInput, @Req() req: Request, ) { return this.dbAi.formula(workspaceId, requireActor(req), body, req); } }