4.1 KiB
4.1 KiB
Phase 4: REST API — query / filter / export / settings / clear
Status: ⬜ Not Started Parent plan: PLAN.md Domain: backend
Objective
Expose the audit log over the REST API: a filtered, keyset-paginated list endpoint; a streaming CSV/JSON export honoring the same filters; retention settings get/update; and a destructive clear. Apply the project's auth posture (stricter auth on export + clear).
Tasks
server/src/ledgrab/api/schemas/activity_log.py(Pydantic):ActivityLogEntryResponse(matches the frozen Phase 2 entry dict shape).ActivityLogPageResponse{entries: list[...],next_before_seq: int | None,total: int(optional/over filters),has_more: bool}.ActivityLogSettingsResponse/UpdateActivityLogSettingsRequest(enabled,max_days,max_entries) with validation bounds.
server/src/ledgrab/api/routes/activity_log.py—APIRouter(prefix="/api/v1/activity-log"):GET ""— list. Query params:categories,severities,actor,entity_type,entity_id,since/until(ISO),q(free-text),before_seq(cursor),limit(default 50, capped e.g. 200).AuthRequired. Maps params →ActivityLogFilters, callsrepo.query(...), returns page envelope.GET "/export"— streaming export. Same filters;format=csv|json. UsesStreamingResponseoverrepo.iter_export(...).require_authenticated()(may contain IPs/labels). SetsContent-Dispositionwith a timestamped filename.GET "/settings"/PUT "/settings"— retention settings via the retention engine.AuthRequired; updates apply immediately.DELETE ""— clear all entries.require_authenticated(). The clear is itself audited (recorder records asystem/activity_log_clearedentry AFTER the wipe, so the log shows who cleared it and when).- Register the router in
server/src/ledgrab/api/__init__.py(aggregator).
- API tests
server/tests/api/routes/test_activity_log_api.py:- list returns entries; each filter narrows results;
before_seqcursor paginates without overlap/gaps;limitcap enforced; - export CSV and JSON both stream and honor filters; export requires authentication (401 for loopback-anonymous when keys configured);
- settings get/update round-trip + validation rejects out-of-range;
- clear empties the log, requires auth, and leaves exactly one post-clear audit entry.
- list returns entries; each filter narrows results;
Files to Modify/Create
server/src/ledgrab/api/schemas/activity_log.py— newserver/src/ledgrab/api/routes/activity_log.py— newserver/src/ledgrab/api/__init__.py— modify: register routerserver/tests/api/routes/test_activity_log_api.py— new
Acceptance Criteria
- List is filterable on every dimension and keyset-paginated (stable, no dupes/gaps).
- Export streams CSV + JSON, honors filters, and requires authentication.
- Settings get/update works and validates bounds; changes take effect immediately.
- Clear requires authentication and is itself audited.
- New + existing tests green;
ruffclean.
Notes
- Auth helpers:
AuthRequireddependency for normal endpoints;require_authenticated()for export + clear (pattern: backup download / secret reveal). Seeapi/auth.py+server/CLAUDE.md. - Follow the existing route/schema conventions (one schema file per entity, router registered
in
api/__init__.py). Referenceapi/routes/backup.pyfor settings-style GET/PUT + aStreamingResponse/download pattern. - Reuse the entry→dict serializer from Phase 2 to keep the response shape single-sourced.
- Backup/restore: no
STORE_MAPchange needed — backup is whole-DB; the table is auto-covered.
Review Checklist
- All tasks completed
- Code follows project conventions (router registration, schema-per-entity, auth posture)
- No unintended side effects
- Build passes (ruff + pytest)
- Tests pass (new + existing)