- Added an audit trail feature that logs every create, update, and delete action on delivery notes and orders, capturing detailed changes per field. - Introduced the `AuditTimeline` component to display the history of changes for delivery notes and orders, enhancing traceability. - Updated the `page.tsx` files for delivery notes and orders to fetch and display audit logs, ensuring users can view the history of modifications. - Enhanced the `package.json` with a new migration script for setting up the audit log functionality. This feature improves accountability and transparency in the management of delivery notes and orders.
115 lines
4 KiB
TypeScript
115 lines
4 KiB
TypeScript
/**
|
|
* One-shot migration: create the `audit_log` collection.
|
|
*
|
|
* Run with:
|
|
*
|
|
* npx tsx scripts/migrations/2026-09-25-create-audit-log.ts
|
|
*
|
|
* One row per create / update / delete on quotes, quote_items,
|
|
* quote_attachments, orders, order_items and delivery_notes. Rows are
|
|
* written ONLY by the PB hook `pocketbase/pb_hooks/audit_log.pb.js`
|
|
* (which runs as the app, so it bypasses the API rules below). Nobody
|
|
* can create, edit or delete an audit row through the API — the
|
|
* create/update/delete rules are superuser-only (null).
|
|
*
|
|
* Read side: `src/lib/server/audit-log-service.ts` (admin client),
|
|
* surfaced on the quote / order / delivery-note detail pages.
|
|
*
|
|
* Idempotent — safe to re-run; exits early if the collection exists.
|
|
*
|
|
* Rollback: remove the hook file from pb_hooks, then delete the
|
|
* `audit_log` collection in the PB admin UI. Nothing references it.
|
|
*/
|
|
import "./_env-loader";
|
|
import PocketBase from "pocketbase";
|
|
|
|
const PB_URL = process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
|
|
const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
|
|
const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
|
|
|
|
// Explicit `max` on every text field: PB treats `max: 0` as a 5000-char
|
|
// cap, not "unlimited", and fails the insert with a generic 400.
|
|
const text = (name: string, max: number, required = false) => ({
|
|
name,
|
|
type: "text",
|
|
required,
|
|
max,
|
|
});
|
|
|
|
async function main() {
|
|
if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
|
throw new Error(
|
|
"Set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD env vars (e.g. sourced from .env.local)."
|
|
);
|
|
}
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
|
console.log(`[audit-log] Authenticated against ${PB_URL}`);
|
|
|
|
const existing = await pb.collections
|
|
.getFullList()
|
|
.then((all) => all.find((c) => c.name === "audit_log"));
|
|
|
|
if (existing) {
|
|
console.log("[audit-log] audit_log already exists — skipping.");
|
|
return;
|
|
}
|
|
|
|
await pb.collections.create({
|
|
name: "audit_log",
|
|
type: "base",
|
|
// Any logged-in dashboard user may read the trail; only the hook
|
|
// (running as the app) may write it.
|
|
listRule: "@request.auth.id != ''",
|
|
viewRule: "@request.auth.id != ''",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
// Which record changed.
|
|
text("collection_name", 64, true),
|
|
text("record_id", 32, true),
|
|
// Which detail page the change rolls up to: the quote for a
|
|
// quote_item, the order for an order_item, etc.
|
|
{
|
|
name: "parent_type",
|
|
type: "select",
|
|
required: true,
|
|
maxSelect: 1,
|
|
values: ["quote", "order", "delivery_note"],
|
|
},
|
|
text("parent_id", 32, true),
|
|
// create | update | delete | pdf_generated | attachment_added
|
|
text("action", 32, true),
|
|
// Human context for the row, e.g. the item description or quote number.
|
|
text("label", 500),
|
|
// [{ field, from, to }] — values truncated by the hook.
|
|
{ name: "changes", type: "json", required: false, maxSize: 200000 },
|
|
// Who: resolved PB user id + display name, or a hint for
|
|
// automations (n8n / agent) that write with the superuser key.
|
|
text("actor_id", 32),
|
|
text("actor_name", 200),
|
|
text("actor_email", 200),
|
|
// web | server | agent | mcp | n8n | system
|
|
text("source", 32),
|
|
// Extra lookup keys, space separated: "dn:DN26071401 quote:abc".
|
|
text("refs", 500),
|
|
{ name: "created", type: "autodate", onCreate: true, onUpdate: false },
|
|
],
|
|
indexes: [
|
|
"CREATE INDEX idx_audit_log_parent ON audit_log (parent_type, parent_id, created)",
|
|
"CREATE INDEX idx_audit_log_record ON audit_log (collection_name, record_id)",
|
|
],
|
|
});
|
|
|
|
console.log("[audit-log] Created audit_log collection. Migration complete.");
|
|
console.log(
|
|
"[audit-log] Next: copy pocketbase/pb_hooks/audit_log.pb.js into the PB server's pb_hooks/ directory and restart PB."
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("[audit-log] Migration failed:", err);
|
|
process.exit(1);
|
|
});
|