/** * TASK-0110 — automation management API (workspace-scoped CRUD, sub-resource * PUTs, status machine, manual run, runs history). All permissions enforced * in the service via `assertCan` (server-side only). * * Public incoming-webhook endpoint: `webhook-incoming.controller.ts` * (separate controller — no auth, signature-verified). */ import { Body, Controller, Delete, Get, HttpCode, Inject, Param, Patch, Post, Put, Query, Req } from "@nestjs/common"; import type { Request } from "express"; import { AutomationsService } from "./automations.service.js"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { requireActor } from "../../common/types.js"; import { CreateAutomationDto, type CreateAutomationInput, ListRunsQuery, type ListRunsQueryInput, PutActionsDto, type PutActionsInput, PutConditionsDto, type PutConditionsInput, PutTriggersDto, type PutTriggersInput, RunInputDto, type RunInputInput, SetStatusDto, type SetStatusInput, UpdateAutomationDto, type UpdateAutomationInput } from "./dto.js"; @Controller() export class AutomationsController { constructor(@Inject(AutomationsService) private readonly automations: AutomationsService) {} // ---------- workspace-scoped (list / create) ---------- @Get("workspaces/:workspaceId/automations") list( @Param("workspaceId") workspaceId: string, @Query() query: { status?: string; cursor?: string; limit?: number }, @Req() req: Request ) { return this.automations.list(workspaceId, requireActor(req), { status: query.status, cursor: query.cursor, limit: query.limit ? Number(query.limit) : undefined }); } @Post("workspaces/:workspaceId/automations") create( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(CreateAutomationDto)) body: CreateAutomationInput, @Req() req: Request ) { return this.automations.create(workspaceId, requireActor(req), body, req); } // ---------- automation-scoped (workspace resolved from the row) ---------- @Get("automations/:id") get(@Param("id") id: string, @Req() req: Request) { return this.automations.get(id, requireActor(req)); } @Patch("automations/:id") update( @Param("id") id: string, @Body(ZodValidate(UpdateAutomationDto)) body: UpdateAutomationInput, @Req() req: Request ) { return this.automations.update(id, requireActor(req), body, req); } /** DELETE → ARCHIVED (soft — runs retained for audit; re-activate allowed). */ @Delete("automations/:id") remove(@Param("id") id: string, @Req() req: Request) { return this.automations.remove(id, requireActor(req), req).then(() => ({ archived: true })); } /** Status machine: POST /automations/:id/status {status} (409 on invalid). */ @Post("automations/:id/status") @HttpCode(200) status( @Param("id") id: string, @Body(ZodValidate(SetStatusDto)) body: SetStatusInput, @Req() req: Request ) { return this.automations.setStatus(id, requireActor(req), body.status, req); } // ---------- sub-resource PUTs (the TASK-0114 editor contract) ---------- /** Replace the trigger set (1..3, OR-semantics). */ @Put("automations/:id/triggers") putTriggers( @Param("id") id: string, @Body(ZodValidate(PutTriggersDto)) body: PutTriggersInput, @Req() req: Request ) { return this.automations.putTriggers(id, requireActor(req), body, req); } /** Replace the condition tree (0..1). */ @Put("automations/:id/conditions") putConditions( @Param("id") id: string, @Body(ZodValidate(PutConditionsDto)) body: PutConditionsInput, @Req() req: Request ) { return this.automations.putConditions(id, requireActor(req), body, req); } /** Replace the ordered action list (1..50). */ @Put("automations/:id/actions") putActions( @Param("id") id: string, @Body(ZodValidate(PutActionsDto)) body: PutActionsInput, @Req() req: Request ) { return this.automations.putActions(id, requireActor(req), body, req); } /** Regenerate the WEBHOOK trigger's secret (old one invalidated). */ @Post("automations/:id/webhook/regenerate") @HttpCode(200) regenerateWebhook(@Param("id") id: string, @Req() req: Request) { return this.automations.regenerateWebhookSecret(id, requireActor(req), req); } // ---------- manual run + runs history (TASK-0114 contract) ---------- /** Manual trigger (DRAFT dry-run allowed) → 202 { runId }. */ @Post("automations/:id/run") @HttpCode(202) run( @Param("id") id: string, @Body(ZodValidate(RunInputDto)) body: RunInputInput, @Req() req: Request ) { return this.automations.manualRun(id, requireActor(req), body, req); } @Get("automations/:id/runs") listRuns( @Param("id") id: string, @Query(ZodValidate(ListRunsQuery)) query: ListRunsQueryInput, @Req() req: Request ) { return this.automations.listRuns(id, requireActor(req), query); } @Get("automations/:id/runs/:runId") getRun(@Param("id") id: string, @Param("runId") runId: string, @Req() req: Request) { return this.automations.getRun(id, runId, requireActor(req)); } /** Retry a FAILED/DEAD_LETTER run (resets retryCount, re-enqueues). */ @Post("automations/:id/runs/:runId/retry") @HttpCode(200) retryRun(@Param("id") id: string, @Param("runId") runId: string, @Req() req: Request) { return this.automations.retryRun(id, runId, requireActor(req), req); } }