import "server-only"; import { Prisma } from "@/generated/prisma/client"; import { prisma } from "@/lib/prisma"; export type AuditEntry = { scope: string; event: string; target?: string | null; actorEmail?: string | null; details?: Record | null; }; export async function recordAudit(entry: AuditEntry): Promise { const safeDetails = (entry.details ?? {}) as Prisma.InputJsonValue; try { await prisma.auditLog.create({ data: { scope: entry.scope, event: entry.event, target: entry.target ?? null, actorEmail: entry.actorEmail ?? null, details: safeDetails, }, }); } catch (e) { console.error( JSON.stringify({ warn: "audit.persist.failed", scope: entry.scope, event: entry.event, target: entry.target ?? null, actorEmail: entry.actorEmail ?? null, details: entry.details ?? {}, error: e instanceof Error ? e.message : String(e), }), ); } } export type AuditFilters = { q?: string; scope?: string; event?: string; actor?: string; from?: Date; to?: Date; }; export type AuditListItem = { id: string; scope: string; event: string; target: string | null; actorEmail: string | null; details: unknown; createdAt: Date; }; export async function listAuditAdmin(filters: AuditFilters = {}): Promise { const where: Prisma.AuditLogWhereInput = {}; if (filters.q) { where.OR = [ { event: { contains: filters.q, mode: "insensitive" } }, { target: { contains: filters.q, mode: "insensitive" } }, { actorEmail: { contains: filters.q, mode: "insensitive" } }, ]; } if (filters.scope) where.scope = filters.scope; if (filters.event) where.event = filters.event; if (filters.actor) where.actorEmail = filters.actor; if (filters.from || filters.to) { where.createdAt = {}; if (filters.from) where.createdAt.gte = filters.from; if (filters.to) where.createdAt.lte = filters.to; } return prisma.auditLog.findMany({ where, orderBy: { createdAt: "desc" }, take: 300, }); } export async function listAuditScopes(): Promise { const rows = await prisma.auditLog.findMany({ distinct: ["scope"], select: { scope: true }, orderBy: { scope: "asc" }, }); return rows.map((r) => r.scope); }