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); }), });