import { Body, Controller, Get, HttpCode, HttpStatus, Inject, NotFoundException, Param, Post, UseGuards, ForbiddenException, } from "@nestjs/common"; import { eq, desc, and } from "drizzle-orm"; import { schema, type Database } from "@courtmitra/db"; import { CaseLookupInput } from "@courtmitra/contracts"; import type { CaseLookupStatus as CLS } 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 CaseLookupController { constructor( @Inject(DB) private readonly db: Database, @Inject(WorkflowService) private readonly workflow: WorkflowService, ) {} /** Verify the mission belongs to the requesting user. */ private async assertMissionOwnership(missionId: string, userId: string) { const [missionRow] = await this.db .select({ userId: schema.missions.userId }) .from(schema.missions) .where(eq(schema.missions.id, missionId)) .limit(1); if (!missionRow) throw new NotFoundException(`Mission not found: ${missionId}`); if (missionRow.userId !== userId) throw new ForbiddenException("You do not own this mission"); } @Post("missions/:missionId/case-lookup") @HttpCode(HttpStatus.ACCEPTED) async lookup(@Param("missionId") missionId: string, @CurrentUser() userId: string, @Body() body: unknown) { const input = parseOrThrow(CaseLookupInput, body); await this.assertMissionOwnership(missionId, userId); await this.workflow.send("case.lookup_requested", { missionId, cnrNumber: input.cnrNumber, }); return { accepted: true, missionId, cnrNumber: input.cnrNumber, message: "Case lookup submitted. Results will be available shortly.", }; } @Get("missions/:missionId/case-lookups") async list(@Param("missionId") missionId: string, @CurrentUser() userId: string) { await this.assertMissionOwnership(missionId, userId); const rows = await this.db .select() .from(schema.caseLookups) .where(eq(schema.caseLookups.missionId, missionId)) .orderBy(desc(schema.caseLookups.createdAt)); return rows.map((r) => ({ id: r.id, missionId: r.missionId, cnrNumber: r.cnrNumber, caseNumber: r.caseNumber, courtName: r.courtName, petitioner: r.petitioner, respondent: r.respondent, caseStatus: r.caseStatus, nextHearingDate: r.nextHearingDate ?? null, lastOrderDate: r.lastOrderDate ?? null, lastOrderSummary: r.lastOrderSummary, source: r.source, lookupStatus: r.lookupStatus as CLS, errorMessage: r.errorMessage, createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString(), })); } @Get("case-lookups/:id") async get(@Param("id") id: string, @CurrentUser() userId: string) { const [row] = await this.db .select() .from(schema.caseLookups) .where(eq(schema.caseLookups.id, id)) .limit(1); if (!row) throw new NotFoundException(`Case lookup not found: ${id}`); await this.assertMissionOwnership(row.missionId, userId); return { id: row.id, missionId: row.missionId, cnrNumber: row.cnrNumber, caseNumber: row.caseNumber, courtName: row.courtName, petitioner: row.petitioner, respondent: row.respondent, caseStatus: row.caseStatus, nextHearingDate: row.nextHearingDate ?? null, lastOrderDate: row.lastOrderDate ?? null, lastOrderSummary: row.lastOrderSummary, source: row.source, lookupStatus: row.lookupStatus as CLS, errorMessage: row.errorMessage, createdAt: row.createdAt.toISOString(), updatedAt: row.updatedAt.toISOString(), rawData: row.rawData, }; } }