karbe/src/lib/admin/audit.ts

91 lines
2.3 KiB
TypeScript

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<string, unknown> | null;
};
export async function recordAudit(entry: AuditEntry): Promise<void> {
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<AuditListItem[]> {
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<string[]> {
const rows = await prisma.auditLog.findMany({
distinct: ["scope"],
select: { scope: true },
orderBy: { scope: "asc" },
});
return rows.map((r) => r.scope);
}