import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { Prisma } from '@b2bcall/database'; import type { QueryAuditDto } from './dto/query-audit.dto'; export interface AuditEntry { userId?: string | null; action: string; entityType?: string; entityId?: string; before?: Prisma.InputJsonValue | null; after?: Prisma.InputJsonValue | null; ipAddress?: string; userAgent?: string; } // Campos que nunca devem ser persistidos em texto puro no before/after do // audit log (agente.md seção 12: "nunca salvar segredos abertos"). const SENSITIVE_KEYS = new Set([ 'password', 'passwordHash', 'secret', 'token', 'refreshToken', 'accessToken', 'amiSecret', 'ariSecret', ]); function redact(value: unknown): unknown { if (value === null || value === undefined) return value; if (Array.isArray(value)) return value.map(redact); if (typeof value === 'object') { const out: Record = {}; for (const [key, val] of Object.entries(value as Record)) { out[key] = SENSITIVE_KEYS.has(key) ? '[REDACTED]' : redact(val); } return out; } return value; } @Injectable() export class AuditService { constructor(private readonly prisma: PrismaService) {} async log(entry: AuditEntry): Promise { await this.prisma.auditLog.create({ data: { userId: entry.userId ?? null, action: entry.action, entityType: entry.entityType, entityId: entry.entityId, before: (redact(entry.before ?? null) as Prisma.InputJsonValue) ?? Prisma.JsonNull, after: (redact(entry.after ?? null) as Prisma.InputJsonValue) ?? Prisma.JsonNull, ipAddress: entry.ipAddress, userAgent: entry.userAgent, }, }); } // Paginação sempre server-side (agente.md seção 54) — audit_logs cresce // sem limite, nunca um SELECT * sem filtro/paginação. async query(query: QueryAuditDto) { const where: Prisma.AuditLogWhereInput = { userId: query.userId, action: query.action, entityType: query.entityType, createdAt: { gte: query.from ? new Date(query.from) : undefined, lte: query.to ? new Date(query.to) : undefined, }, }; const [total, items] = await this.prisma.$transaction([ this.prisma.auditLog.count({ where }), this.prisma.auditLog.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (query.page - 1) * query.pageSize, take: query.pageSize, include: { user: { select: { id: true, name: true, email: true } } }, }), ]); return { items: items.map((item) => ({ ...item, id: item.id.toString() })), total, page: query.page, pageSize: query.pageSize, }; } }