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

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.pyAPIRouter(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