import { Controller, ForbiddenException, Get, Query, UseGuards } from "@nestjs/common"; import { getPrismaClient } from "@b2bcall/database"; import { isPlatformUser, type AccessTokenClaims } from "@b2bcall/auth"; import { JwtAuthGuard } from "../common/guards/jwt-auth.guard"; import { PermissionGuard } from "../common/guards/permission.guard"; import { RequirePermission } from "../common/decorators/require-permission.decorator"; import { CurrentUser } from "../common/decorators/current-user.decorator"; /** * "Sistema > Auditoria" (agente.md secao 150-151, 168) — `audit_logs` não * tem RLS (linha imutável de auditoria, precisa sobreviver mesmo que o * tenant seja apagado — decisão do schema desde a PHASE 04), então uma * consulta cross-tenant direta é segura aqui; só platform admin acessa * este endpoint (um tenant admin vê o próprio audit trail por outro * caminho, se/quando existir). */ @UseGuards(JwtAuthGuard, PermissionGuard) @Controller("platform/audit-log") export class PlatformAuditController { @RequirePermission("audit.view") @Get() async list( @CurrentUser() user: AccessTokenClaims, @Query("action") action?: string, @Query("tenantId") tenantId?: string, ): Promise[]> { if (!(await isPlatformUser(user.sub))) { throw new ForbiddenException("So' um usuario com role de plataforma pode ver o audit log da plataforma"); } const prisma = getPrismaClient(); const entries = await prisma.auditLog.findMany({ where: { ...(action ? { action: { contains: action, mode: "insensitive" } } : {}), ...(tenantId ? { tenantId } : {}), }, orderBy: { createdAt: "desc" }, take: 200, }); const userIds = Array.from(new Set(entries.map((e) => e.userId).filter((id): id is string => id != null))); const tenantIds = Array.from(new Set(entries.map((e) => e.tenantId).filter((id): id is string => id != null))); const [users, tenants] = await Promise.all([ userIds.length ? prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, email: true } }) : [], tenantIds.length ? prisma.tenant.findMany({ where: { id: { in: tenantIds } }, select: { id: true, legalName: true } }) : [], ]); const userEmail = new Map(users.map((u) => [u.id, u.email])); const tenantName = new Map(tenants.map((t) => [t.id, t.legalName])); return entries.map((e) => ({ ...e, userEmail: e.userId ? (userEmail.get(e.userId) ?? e.userId) : null, tenantName: e.tenantId ? (tenantName.get(e.tenantId) ?? e.tenantId) : null, })); } }