feat(audit): per-record change-history endpoint (PMS-182) #154

Merged
longjacksonle merged 1 commit from feat/pms-182-ticket-edit-history into main 2026-06-11 04:05:23 +02:00

What

Adds a per-record change-history endpoint so the SPA detail pages can show an entity's edit history to any tenant member (not just admins). Backend half of PMS-182 (tickets), reused by PMS-184 (tasks) and assets.

Endpoint

GET /api/v1/audit-log/entity/{entity_type}/{entity_id}

  • Tenant-scoped and non-admin (RequireAuth only): a user can review the history of a record they can already see, without gaining the full tenant audit trail that /audit-log (admin-only) exposes.
  • Whitelisted entity types: tickets, tasks, projects, assets. Any other type is a 404, so the endpoint can never be pointed at billing/auth trails.
  • Reads the existing audit_log rows filtered by entity_id. The audit middleware writes entity_id-less rows on every mutation; those are naturally excluded, so only the explicit before/after snapshots (e.g. update_ticket's audit_write) surface.

Response

Each EntityHistoryEntry carries action, user_id, timestamp, and changed_fields: the set of columns that differ between the before/after JSON snapshots, with bookkeeping noise (updated_at, created_at, id, tenant_id, last_updated_by_id) removed and sorted. So a client can render "Updated (description, status)" without parsing raw JSON.

Tests

ticket_history_records_description_edit: create a ticket, PUT a new description, then assert the history endpoint returns an update entry whose changed_fields contains description, and that a non-whitelisted entity type returns 404.

🤖 Generated with Claude Code

## What Adds a per-record change-history endpoint so the SPA detail pages can show an entity's edit history to any tenant member (not just admins). Backend half of PMS-182 (tickets), reused by PMS-184 (tasks) and assets. ## Endpoint `GET /api/v1/audit-log/entity/{entity_type}/{entity_id}` - **Tenant-scoped** and **non-admin** (RequireAuth only): a user can review the history of a record they can already see, without gaining the full tenant audit trail that `/audit-log` (admin-only) exposes. - **Whitelisted** entity types: `tickets`, `tasks`, `projects`, `assets`. Any other type is a `404`, so the endpoint can never be pointed at billing/auth trails. - Reads the existing `audit_log` rows filtered by `entity_id`. The audit middleware writes `entity_id`-less rows on every mutation; those are naturally excluded, so only the explicit before/after snapshots (e.g. `update_ticket`'s `audit_write`) surface. ## Response Each `EntityHistoryEntry` carries `action`, `user_id`, `timestamp`, and `changed_fields`: the set of columns that differ between the before/after JSON snapshots, with bookkeeping noise (`updated_at`, `created_at`, `id`, `tenant_id`, `last_updated_by_id`) removed and sorted. So a client can render "Updated (description, status)" without parsing raw JSON. ## Tests `ticket_history_records_description_edit`: create a ticket, PUT a new description, then assert the history endpoint returns an `update` entry whose `changed_fields` contains `description`, and that a non-whitelisted entity type returns `404`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(audit): per-record change-history endpoint for detail pages (PMS-182)
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 34s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m24s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m31s
Create release / Create release from merged PR (pull_request) Has been skipped
110f5329ea
Add GET /api/v1/audit-log/entity/{entity_type}/{entity_id}: a tenant-scoped, non-admin per-record change history derived from the existing audit_log. Unlike the admin-only /audit-log, this lets any authenticated tenant member review the edit history of a record they can already see, but only for a whitelist of entity types (tickets, tasks, projects, assets) so it never exposes the trail for sensitive entities, and an unknown type is a 404. Each entry carries the action, actor id, timestamp, and the set of changed columns (computed by diffing the before/after JSON snapshots and dropping bookkeeping noise like updated_at), so a detail page can render "Updated (description, status) by X" without parsing raw JSON. update_ticket already writes the before/after audit row this reads; the middleware's entity_id-less rows are naturally excluded by the entity_id filter. Backs the ticket description history in PMS-182 and is reused by PMS-184 (tasks) and assets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
longjacksonle deleted branch feat/pms-182-ticket-edit-history 2026-06-11 04:05:23 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!154
No description provided.