import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req, Res, StreamableFile, UploadedFile, UseGuards, UseInterceptors, } from "@nestjs/common"; import { FileInterceptor } from "@nestjs/platform-express"; import { ServiceKind } from "@jorgecuadros/database"; import { Request, Response } from "express"; import { downloadName, type UploadedFileLike } from "../storage/upload-file"; import { AuthenticatedGuard } from "../auth/authenticated.guard"; import { AbilityGuard } from "../auth/ability.guard"; import { RequireAbility } from "../auth/require-ability.decorator"; import { AuditService } from "../common/audit.service"; import { PropertiesService, type PropertySort, type TrustFilter, } from "./properties.service"; import { CreatePropertyDto, ServiceDto, TrustDto, UpdatePropertyDto, UpdateServiceDto, } from "./property.dto"; const KINDS: ServiceKind[] = [ "WATER", "ELECTRIC", "GAS", "CABLE", "PROPERTY_TAX", "FEDERAL_ZONE", "ALARM", "OTHER", ]; const TRUST_FILTERS: TrustFilter[] = [ "with", "without", "active", "expiring", "expired", "undated", ]; const SORTS: PropertySort[] = [ "customer", "address", "services_desc", "trust_due_asc", "trust_due_desc", ]; function parseDays(days?: string): number { return Math.min(365, Math.max(1, Number(days) || 30)); } @UseGuards(AuthenticatedGuard, AbilityGuard) @Controller("properties") export class PropertiesController { constructor( private readonly properties: PropertiesService, private readonly audit: AuditService, ) {} private actingId(req: Request): string { return (req.user as { id: string }).id; } @Get("stats") stats(@Query("days") days?: string) { return this.properties.stats(parseDays(days)); } @Get("facets") facets() { return this.properties.facets(); } @Get() list( @Query("query") query?: string, @Query("page") page?: string, @Query("pageSize") pageSize?: string, @Query("serviceKind") serviceKind?: string, @Query("municipality") municipality?: string, @Query("bank") bank?: string, @Query("trust") trust?: string, @Query("hasServices") hasServices?: string, @Query("customerId") customerId?: string, @Query("days") days?: string, @Query("includeArchived") includeArchived?: string, @Query("sort") sort?: string, ) { return this.properties.list({ query, page: Math.max(1, Number(page) || 1), pageSize: Math.min(100, Math.max(1, Number(pageSize) || 25)), serviceKind: KINDS.includes(serviceKind as ServiceKind) ? (serviceKind as ServiceKind) : undefined, municipality: municipality || undefined, bank: bank || undefined, trust: TRUST_FILTERS.includes(trust as TrustFilter) ? (trust as TrustFilter) : undefined, hasServices: hasServices === "true" ? true : hasServices === "false" ? false : undefined, customerId: customerId || undefined, days: parseDays(days), includeArchived: includeArchived === "true", sort: SORTS.includes(sort as PropertySort) ? (sort as PropertySort) : "customer", }); } @Get(":id") detail(@Param("id") id: string, @Query("days") days?: string) { return this.properties.detail(id, parseDays(days)); } // --- header writes -------------------------------------------------------- @Post() @RequireAbility("property:create") async create(@Body() dto: CreatePropertyDto, @Req() req: Request) { const p = await this.properties.create(dto); void this.audit.log(this.actingId(req), "property.create", { propertyId: p.id }); return p; } @Patch(":id") @RequireAbility("property:update") async update(@Param("id") id: string, @Body() dto: UpdatePropertyDto, @Req() req: Request) { const p = await this.properties.update(id, dto); void this.audit.log(this.actingId(req), "property.update", { propertyId: id }); return p; } @Delete(":id") @RequireAbility("property:delete") async archive(@Param("id") id: string, @Req() req: Request) { const p = await this.properties.archive(id); void this.audit.log(this.actingId(req), "property.archive", { propertyId: id }); return p; } @Post(":id/restore") @RequireAbility("property:delete") async restore(@Param("id") id: string, @Req() req: Request) { const p = await this.properties.restore(id); void this.audit.log(this.actingId(req), "property.restore", { propertyId: id }); return p; } // --- services (property:update) ------------------------------------------- @Post(":id/services") @RequireAbility("property:update") addService(@Param("id") id: string, @Body() dto: ServiceDto) { return this.properties.addService(id, dto); } @Patch(":id/services/:childId") @RequireAbility("property:update") updateService( @Param("id") id: string, @Param("childId") childId: string, @Body() dto: UpdateServiceDto, ) { return this.properties.updateService(id, childId, dto); } @Delete(":id/services/:childId") @RequireAbility("property:update") removeService(@Param("id") id: string, @Param("childId") childId: string) { return this.properties.removeService(id, childId); } // --- trust account (1:1) -------------------------------------------------- @Put(":id/trust") @RequireAbility("property:update") upsertTrust(@Param("id") id: string, @Body() dto: TrustDto) { return this.properties.upsertTrust(id, dto); } @Delete(":id/trust") @RequireAbility("property:update") removeTrust(@Param("id") id: string) { return this.properties.removeTrust(id); } // --- documents ------------------------------------------------------------ @Post(":id/documents") @RequireAbility("property:update") @UseInterceptors( FileInterceptor("file", { limits: { fileSize: 50 * 1024 * 1024 } }), ) addDocument( @Param("id") id: string, @UploadedFile() file: UploadedFileLike | undefined, @Query("type") type: string | undefined, ) { if (!file) throw new Error("No se recibió ningún archivo."); return this.properties.addDocument(id, file, type); } @Get(":id/documents/:childId/download") async downloadDocument( @Param("id") id: string, @Param("childId") childId: string, @Res({ passthrough: true }) res: Response, ): Promise { const { row, stream, contentType } = await this.properties.getDocument(id, childId); res.set({ "Content-Type": contentType ?? "application/octet-stream", "Content-Disposition": `attachment; filename="${downloadName(row.storageKey, row.documentType)}"`, }); return new StreamableFile(stream); } @Delete(":id/documents/:childId") @RequireAbility("property:update") removeDocument(@Param("id") id: string, @Param("childId") childId: string) { return this.properties.removeDocument(id, childId); } }