Files
Nexus/packages/api/src/router/audit-log.ts
T

146 lines
4.3 KiB
TypeScript

import { z } from "zod";
import { createTRPCRouter, controllerProcedure } from "../trpc.js";
import {
auditLogByEntityInputSchema,
auditLogListInputSchema,
auditLogTimelineInputSchema,
} from "./audit-log-inputs.js";
import {
formatAuditDetailEntry,
formatAuditListEntry,
getAuditActivitySummary,
getAuditEntriesByEntity,
getAuditEntryById,
getAuditTimeline,
listAuditEntries,
toAuditListInput,
toAuditTimelineInput,
} from "./audit-log-support.js";
// ─── Router ───────────────────────────────────────────────────────────────────
export const auditLogRouter = createTRPCRouter({
/**
* Paginated, filterable list of audit log entries.
* Cursor-based pagination using createdAt + id.
*/
list: controllerProcedure
.input(auditLogListInputSchema)
.query(async ({ ctx, input }) => {
return listAuditEntries(ctx.db, toAuditListInput({
entityType: input.entityType,
entityId: input.entityId,
userId: input.userId,
action: input.action,
source: input.source,
startDate: input.startDate,
endDate: input.endDate,
search: input.search,
limit: input.limit,
cursor: input.cursor,
}));
}),
listDetail: controllerProcedure
.input(auditLogListInputSchema)
.query(async ({ ctx, input }) => {
const result = await listAuditEntries(ctx.db, toAuditListInput({
entityType: input.entityType,
entityId: input.entityId,
userId: input.userId,
action: input.action,
source: input.source,
startDate: input.startDate,
endDate: input.endDate,
search: input.search,
limit: input.limit,
cursor: input.cursor,
}));
return {
items: result.items.map(formatAuditListEntry),
nextCursor: result.nextCursor ?? null,
};
}),
/**
* Get a single audit entry with full changes JSONB (for expand/detail view).
*/
getById: controllerProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
return getAuditEntryById(ctx.db, input.id);
}),
getByIdDetail: controllerProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const entry = await getAuditEntryById(ctx.db, input.id);
return formatAuditDetailEntry(entry);
}),
/**
* Get all audit entries for a specific entity (e.g. a project or resource).
*/
getByEntity: controllerProcedure
.input(auditLogByEntityInputSchema)
.query(async ({ ctx, input }) => {
return getAuditEntriesByEntity(ctx.db, input);
}),
getByEntityDetail: controllerProcedure
.input(auditLogByEntityInputSchema)
.query(async ({ ctx, input }) => {
const entries = await getAuditEntriesByEntity(ctx.db, input);
return {
entityType: input.entityType,
entityId: input.entityId,
entityName: entries[0]?.entityName ?? null,
itemCount: entries.length,
items: entries.map(formatAuditDetailEntry),
};
}),
/**
* Timeline view: entries grouped by date (YYYY-MM-DD).
*/
getTimeline: controllerProcedure
.input(auditLogTimelineInputSchema)
.query(async ({ ctx, input }) => {
return getAuditTimeline(ctx.db, toAuditTimelineInput({
startDate: input.startDate,
endDate: input.endDate,
limit: input.limit,
}));
}),
getTimelineDetail: controllerProcedure
.input(auditLogTimelineInputSchema)
.query(async ({ ctx, input }) => {
const timeline = await getAuditTimeline(ctx.db, toAuditTimelineInput({
startDate: input.startDate,
endDate: input.endDate,
limit: input.limit,
}));
return Object.fromEntries(
Object.entries(timeline).map(([dateKey, entries]) => [
dateKey,
entries.map(formatAuditDetailEntry),
]),
);
}),
/**
* Activity summary: counts by entity type, action, and user for a date range.
*/
getActivitySummary: controllerProcedure
.input(
z.object({
startDate: z.date().optional(),
endDate: z.date().optional(),
}),
)
.query(async ({ ctx, input }) => {
return getAuditActivitySummary(ctx.db, input);
}),
});