91 lines
2.3 KiB
TypeScript
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);
|
|
}
|