refactor(api): extract audit log procedures
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user