refactor(api): extract audit log procedures

This commit is contained in:
2026-03-31 21:11:19 +02:00
parent cb12536cdf
commit 5a79ba5843
2 changed files with 151 additions and 114 deletions
@@ -0,0 +1,127 @@
import { z } from "zod";
import type { TRPCContext } 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";
export { auditLogByEntityInputSchema, auditLogListInputSchema, auditLogTimelineInputSchema };
export const auditLogEntryByIdInputSchema = z.object({
id: z.string(),
});
export const auditLogActivitySummaryInputSchema = z.object({
startDate: z.date().optional(),
endDate: z.date().optional(),
});
type AuditLogContext = Pick<TRPCContext, "db">;
export async function listAuditLogEntries(
ctx: AuditLogContext,
input: z.infer<typeof auditLogListInputSchema>,
) {
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,
}));
}
export async function listAuditLogEntriesDetail(
ctx: AuditLogContext,
input: z.infer<typeof auditLogListInputSchema>,
) {
const result = await listAuditLogEntries(ctx, input);
return {
items: result.items.map(formatAuditListEntry),
nextCursor: result.nextCursor ?? null,
};
}
export async function getAuditLogEntryById(
ctx: AuditLogContext,
input: z.infer<typeof auditLogEntryByIdInputSchema>,
) {
return getAuditEntryById(ctx.db, input.id);
}
export async function getAuditLogEntryByIdDetail(
ctx: AuditLogContext,
input: z.infer<typeof auditLogEntryByIdInputSchema>,
) {
const entry = await getAuditEntryById(ctx.db, input.id);
return formatAuditDetailEntry(entry);
}
export async function getAuditLogEntriesByEntity(
ctx: AuditLogContext,
input: z.infer<typeof auditLogByEntityInputSchema>,
) {
return getAuditEntriesByEntity(ctx.db, input);
}
export async function getAuditLogEntriesByEntityDetail(
ctx: AuditLogContext,
input: z.infer<typeof auditLogByEntityInputSchema>,
) {
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),
};
}
export async function getAuditLogTimeline(
ctx: AuditLogContext,
input: z.infer<typeof auditLogTimelineInputSchema>,
) {
return getAuditTimeline(ctx.db, toAuditTimelineInput({
startDate: input.startDate,
endDate: input.endDate,
limit: input.limit,
}));
}
export async function getAuditLogTimelineDetail(
ctx: AuditLogContext,
input: z.infer<typeof auditLogTimelineInputSchema>,
) {
const timeline = await getAuditLogTimeline(ctx, input);
return Object.fromEntries(
Object.entries(timeline).map(([dateKey, entries]) => [
dateKey,
entries.map(formatAuditDetailEntry),
]),
);
}
export async function getAuditLogActivitySummary(
ctx: AuditLogContext,
input: z.infer<typeof auditLogActivitySummaryInputSchema>,
) {
return getAuditActivitySummary(ctx.db, input);
}
+24 -114
View File
@@ -1,145 +1,55 @@
import { z } from "zod";
import { createTRPCRouter, controllerProcedure } from "../trpc.js"; import { createTRPCRouter, controllerProcedure } from "../trpc.js";
import { import {
auditLogActivitySummaryInputSchema,
auditLogByEntityInputSchema, auditLogByEntityInputSchema,
auditLogEntryByIdInputSchema,
auditLogListInputSchema, auditLogListInputSchema,
auditLogTimelineInputSchema, auditLogTimelineInputSchema,
} from "./audit-log-inputs.js"; getAuditLogActivitySummary,
import { getAuditLogEntriesByEntity,
formatAuditDetailEntry, getAuditLogEntriesByEntityDetail,
formatAuditListEntry, getAuditLogEntryById,
getAuditActivitySummary, getAuditLogEntryByIdDetail,
getAuditEntriesByEntity, getAuditLogTimeline,
getAuditEntryById, getAuditLogTimelineDetail,
getAuditTimeline, listAuditLogEntries,
listAuditEntries, listAuditLogEntriesDetail,
toAuditListInput, } from "./audit-log-procedure-support.js";
toAuditTimelineInput,
} from "./audit-log-support.js";
// ─── Router ───────────────────────────────────────────────────────────────────
export const auditLogRouter = createTRPCRouter({ export const auditLogRouter = createTRPCRouter({
/**
* Paginated, filterable list of audit log entries.
* Cursor-based pagination using createdAt + id.
*/
list: controllerProcedure list: controllerProcedure
.input(auditLogListInputSchema) .input(auditLogListInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => listAuditLogEntries(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 listDetail: controllerProcedure
.input(auditLogListInputSchema) .input(auditLogListInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => listAuditLogEntriesDetail(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 getById: controllerProcedure
.input(z.object({ id: z.string() })) .input(auditLogEntryByIdInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => getAuditLogEntryById(ctx, input)),
return getAuditEntryById(ctx.db, input.id);
}),
getByIdDetail: controllerProcedure getByIdDetail: controllerProcedure
.input(z.object({ id: z.string() })) .input(auditLogEntryByIdInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => getAuditLogEntryByIdDetail(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 getByEntity: controllerProcedure
.input(auditLogByEntityInputSchema) .input(auditLogByEntityInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => getAuditLogEntriesByEntity(ctx, input)),
return getAuditEntriesByEntity(ctx.db, input);
}),
getByEntityDetail: controllerProcedure getByEntityDetail: controllerProcedure
.input(auditLogByEntityInputSchema) .input(auditLogByEntityInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => getAuditLogEntriesByEntityDetail(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 getTimeline: controllerProcedure
.input(auditLogTimelineInputSchema) .input(auditLogTimelineInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => getAuditLogTimeline(ctx, input)),
return getAuditTimeline(ctx.db, toAuditTimelineInput({
startDate: input.startDate,
endDate: input.endDate,
limit: input.limit,
}));
}),
getTimelineDetail: controllerProcedure getTimelineDetail: controllerProcedure
.input(auditLogTimelineInputSchema) .input(auditLogTimelineInputSchema)
.query(async ({ ctx, input }) => { .query(({ ctx, input }) => getAuditLogTimelineDetail(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 getActivitySummary: controllerProcedure
.input( .input(auditLogActivitySummaryInputSchema)
z.object({ .query(({ ctx, input }) => getAuditLogActivitySummary(ctx, input)),
startDate: z.date().optional(),
endDate: z.date().optional(),
}),
)
.query(async ({ ctx, input }) => {
return getAuditActivitySummary(ctx.db, input);
}),
}); });