import { prisma } from '../prisma.js'; /** * Record an audit log entry. Non-blocking: catches and swallows errors * to avoid slowing down the operation being audited. */ export function logAction( userId: string | null, action: string, entityType: string, entityId: string, details?: Record ): void { prisma.auditLog .create({ data: { userId, action, entityType, entityId, details: details ? JSON.stringify(details) : '{}' } }) .catch((err) => { // Non-blocking but observable: log to stderr so a failing audit trail // (e.g. DB full, locked) is visible in container logs. console.warn( `[audit] failed to record ${action} ${entityType}:${entityId}:`, err ); }); } /** * Query audit logs with filters and pagination. */ export async function getAuditLogs(options?: { action?: string; entityType?: string; userId?: string; startDate?: string; endDate?: string; limit?: number; offset?: number; }) { const where: Record = {}; if (options?.action) { where.action = options.action; } if (options?.entityType) { where.entityType = options.entityType; } if (options?.userId) { where.userId = options.userId; } const dateFilter: Record = {}; if (options?.startDate) { dateFilter.gte = new Date(options.startDate); } if (options?.endDate) { dateFilter.lte = new Date(options.endDate); } if (Object.keys(dateFilter).length > 0) { where.createdAt = dateFilter; } const limit = options?.limit ?? 50; const offset = options?.offset ?? 0; const [logs, total] = await Promise.all([ prisma.auditLog.findMany({ where, orderBy: { createdAt: 'desc' }, take: limit, skip: offset, include: { user: { select: { id: true, displayName: true, email: true } } } }), prisma.auditLog.count({ where }) ]); return { logs, total }; } /** * Delete audit logs older than the given retention period. */ export async function pruneOldLogs(retentionDays: number = 90) { const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000); const result = await prisma.auditLog.deleteMany({ where: { createdAt: { lt: cutoff } } }); return result.count; }