Files
ledgrab/plans/activity-log/phase-4-api.md
T

80 lines
4.1 KiB
Markdown

# Phase 4: REST API — query / filter / export / settings / clear
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./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`,
calls `repo.query(...)`, returns page envelope.
- `GET "/export"` — streaming export. Same filters; `format=csv|json`. Uses
`StreamingResponse` over `repo.iter_export(...)`. **`require_authenticated()`** (may
contain IPs/labels). Sets `Content-Disposition` with 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 a `system`/`activity_log_cleared` entry 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_seq` cursor paginates without
overlap/gaps; `limit` cap 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.
## Files to Modify/Create
- `server/src/ledgrab/api/schemas/activity_log.py` — new
- `server/src/ledgrab/api/routes/activity_log.py` — new
- `server/src/ledgrab/api/__init__.py` — modify: register router
- `server/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; `ruff` clean.
## Notes
- Auth helpers: `AuthRequired` dependency for normal endpoints; `require_authenticated()` for
export + clear (pattern: backup download / secret reveal). See `api/auth.py` + `server/CLAUDE.md`.
- Follow the existing route/schema conventions (one schema file per entity, router registered
in `api/__init__.py`). Reference `api/routes/backup.py` for settings-style GET/PUT + a
`StreamingResponse`/download pattern.
- Reuse the entry→dict serializer from Phase 2 to keep the response shape single-sourced.
- Backup/restore: no `STORE_MAP` change 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)
## Handoff to Next Phase
<!-- Filled in by the implementer: exact endpoint paths, query-param names, page-envelope
field names, and settings field bounds — Phase 5/6 frontend consumes these verbatim. -->