import { Body, Controller, Headers, HttpCode, HttpStatus, Inject, NotFoundException, Param, Post, UseGuards, ForbiddenException } from "@nestjs/common"; import { eq } from "drizzle-orm"; import { schema, type Database } from "@courtmitra/db"; import { IngestReplyInput } from "@courtmitra/contracts"; import { parseOrThrow } from "../common/zod-validation.pipe.js"; import { DB } from "../db/db.module.js"; import { WorkflowService } from "../workflow/inngest.module.js"; import { CurrentUser } from "../auth/current-user.decorator.js"; import { ClerkAuthGuard } from "../auth/clerk-auth.guard.js"; @UseGuards(ClerkAuthGuard) @Controller() export class RepliesController { constructor( @Inject(DB) private readonly db: Database, @Inject(WorkflowService) private readonly workflow: WorkflowService, ) {} @Post("missions/:missionId/replies/ingest") @HttpCode(HttpStatus.ACCEPTED) async ingest( @Param("missionId") missionId: string, @CurrentUser() userId: string, @Body() body: unknown, @Headers("idempotency-key") _idempotencyKey?: string, ) { const input = parseOrThrow(IngestReplyInput, body); const [mission] = await this.db .select({ id: schema.missions.id, userId: schema.missions.userId }) .from(schema.missions) .where(eq(schema.missions.id, missionId)) .limit(1); if (!mission) throw new NotFoundException(`Mission not found: ${missionId}`); if (mission.userId !== userId) throw new ForbiddenException("You do not own this mission"); await this.workflow.send("email.reply_received", { missionId, actionId: "", from: input.from ?? "user", subject: input.subject ?? "", body: input.body, messageId: _idempotencyKey ? `manual_${_idempotencyKey}` : `manual_${Date.now()}`, replyToken: "", toAddress: "", }); return { ok: true, missionId }; } }