feat: complete audit coverage — comment, webhook, system-role, dispo, scenario

- comment.ts: create (body preview), resolve, delete
- webhook.ts: create, update, delete, test (result in summary)
- system-role-config.ts: update with before/after
- dispo.ts: commitImportBatch (IMPORT with counts), cancelImportBatch
- scenario.ts: applyScenario (CREATE with allocation count)

Audit coverage now: 29/36 routers (81%). Remaining 7 are read-only
(dashboard, staffing, chargeability-report, computation-graph,
report, insights.detectAnomalies, notification read/dismiss).

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-22 22:46:34 +01:00
parent 66878f18f4
commit 7a7430851c
5 changed files with 157 additions and 8 deletions
+59 -4
View File
@@ -2,6 +2,7 @@ import { z } from "zod";
import { TRPCError } from "@trpc/server";
import { createTRPCRouter, adminProcedure } from "../trpc.js";
import { WEBHOOK_EVENTS } from "../lib/webhook-dispatcher.js";
import { createAuditEntry } from "../lib/audit.js";
const webhookEventEnum = z.enum(WEBHOOK_EVENTS as unknown as [string, ...string[]]);
@@ -36,7 +37,7 @@ export const webhookRouter = createTRPCRouter({
}),
)
.mutation(async ({ ctx, input }) => {
return ctx.db.webhook.create({
const webhook = await ctx.db.webhook.create({
data: {
name: input.name,
url: input.url,
@@ -45,6 +46,19 @@ export const webhookRouter = createTRPCRouter({
isActive: input.isActive,
},
});
void createAuditEntry({
db: ctx.db,
entityType: "Webhook",
entityId: webhook.id,
entityName: webhook.name,
action: "CREATE",
userId: ctx.dbUser?.id,
after: webhook as unknown as Record<string, unknown>,
source: "ui",
});
return webhook;
}),
/** Update an existing webhook. */
@@ -67,7 +81,7 @@ export const webhookRouter = createTRPCRouter({
throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
}
return ctx.db.webhook.update({
const updated = await ctx.db.webhook.update({
where: { id: input.id },
data: {
...(input.data.name !== undefined ? { name: input.data.name } : {}),
@@ -77,6 +91,20 @@ export const webhookRouter = createTRPCRouter({
...(input.data.isActive !== undefined ? { isActive: input.data.isActive } : {}),
},
});
void createAuditEntry({
db: ctx.db,
entityType: "Webhook",
entityId: input.id,
entityName: updated.name,
action: "UPDATE",
userId: ctx.dbUser?.id,
before: existing as unknown as Record<string, unknown>,
after: updated as unknown as Record<string, unknown>,
source: "ui",
});
return updated;
}),
/** Delete a webhook. */
@@ -88,6 +116,17 @@ export const webhookRouter = createTRPCRouter({
throw new TRPCError({ code: "NOT_FOUND", message: "Webhook not found" });
}
await ctx.db.webhook.delete({ where: { id: input.id } });
void createAuditEntry({
db: ctx.db,
entityType: "Webhook",
entityId: input.id,
entityName: existing.name,
action: "DELETE",
userId: ctx.dbUser?.id,
before: existing as unknown as Record<string, unknown>,
source: "ui",
});
}),
/** Send a test payload to a webhook URL. */
@@ -127,6 +166,8 @@ export const webhookRouter = createTRPCRouter({
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5_000);
let result: { success: boolean; statusCode: number; statusText: string };
try {
const response = await fetch(wh.url, {
method: "POST",
@@ -134,13 +175,13 @@ export const webhookRouter = createTRPCRouter({
body,
signal: controller.signal,
});
return {
result = {
success: response.ok,
statusCode: response.status,
statusText: response.statusText,
};
} catch (err) {
return {
result = {
success: false,
statusCode: 0,
statusText: err instanceof Error ? err.message : "Unknown error",
@@ -148,5 +189,19 @@ export const webhookRouter = createTRPCRouter({
} finally {
clearTimeout(timeout);
}
void createAuditEntry({
db: ctx.db,
entityType: "Webhook",
entityId: wh.id,
entityName: wh.name,
action: "UPDATE",
userId: ctx.dbUser?.id,
summary: `Tested webhook (result: ${result.success ? "success" : "failed"})`,
metadata: result as unknown as Record<string, unknown>,
source: "ui",
});
return result;
}),
});