- remove plans/activity-log/ (feature merged; learnings in CLAUDE.md + git history) - server/CLAUDE.md: Activity/Audit Log architecture + extension points (recorder, fire_entity_event hook, sanitize_display, events allowlist, retention, API auth posture)
6.9 KiB
Claude Instructions for LedGrab Server
Project Structure
src/ledgrab/main.py— FastAPI application entry pointsrc/ledgrab/api/routes/— REST API endpoints (one file per entity)src/ledgrab/api/schemas/— Pydantic request/response models (one file per entity)src/ledgrab/core/— Core business logic (capture, devices, audio, processing, automations)src/ledgrab/storage/— Data models (dataclasses) and SQLite-backed persistence stores (BaseSqliteStore)src/ledgrab/utils/— Utility functions (logging, monitor detection, SSRF validation, sound playback)src/ledgrab/static/— Frontend files (TypeScript, CSS, locales)src/ledgrab/templates/— Jinja2 HTML templatesconfig/— Configuration files (YAML)data/— Runtime data: SQLite database (ledgrab.db) + assets. Relocate the root withLEDGRAB_DATA_DIR.
Entity & Storage Pattern
Each entity follows: dataclass model (storage/) + SQLite store (storage/*_store.py, subclassing BaseSqliteStore) + Pydantic schemas (api/schemas/) + routes (api/routes/).
Stores keep an in-memory write-through cache over a per-entity SQLite table (the legacy BaseJsonStore still exists for reference but new stores use BaseSqliteStore). Schema/data shape changes go through storage/data_migrations.py — migrations are idempotent and tracked in a dedicated data_migrations audit table, so they run safely on every startup. When renaming or restructuring stored fields, add a migration there (see the Data Migration Policy in the root CLAUDE.md).
Authentication
API key authentication via Bearer token in the Authorization header (Authorization: Bearer <key>). WebSocket connections authenticate with a first-message handshake ({"type":"auth","token":"<key>"}). See src/ledgrab/api/auth.py for the canonical logic.
- Config:
config/default_config.yamlunderauth.api_keys; env varLEDGRAB_AUTH__API_KEYS - When
api_keysis empty (default): loopback requests (127.0.0.1/::1/localhost) are allowed anonymously, but LAN / remote requests are rejected with401. Auth is not fully open. - When
api_keysis set: a valid Bearer token is required from every client (loopback included). require_authenticated()rejects even loopback-anonymous callers on sensitive endpoints (e.g. backup download, secret reveal).
Activity / Audit Log
Persistent, queryable audit log of meaningful actions (auth, device, entity CRUD, capture, system), surfaced in the WebUI (Activity tab + Dashboard widget + Settings retention panel).
- Storage is NOT a
BaseSqliteStore.storage/activity_log_repository.pyis a purpose-built repository over a dedicated indexedactivity_logtable (migration002_add_activity_log) — query-on-demand with keyset pagination (seqcursor), never load-all-into-memory. Don't route it through the entity-store pattern. - Recording.
core/activity_log/recorder.py(ActivityRecorder) is best-effort (never raises into the audited action) and thread-safe (inline on the event loop;loop.call_soon_threadsafefrom non-loop threads, e.g. zeroconf discovery). It persists the entry and fires anactivity_loggedrealtime event. Actor comes from thecurrent_actorContextVar(set inverify_api_key), default"system". - Entity CRUD is auto-audited via the
fire_entity_event()choke point inapi/dependencies.py— every create/update/delete already calls it. Delete handlers must passentity_name(the entity is gone by record time). Non-entity events use explicitrecorder.record(...)(get it viaget_activity_recorder()DI orget_module_recorder()for engine/thread sites). - Never log secrets. API-key tokens are never recorded. Wrap any untrusted/attacker-controllable string (mDNS names, headers, user-authored names) with
sanitize_display()(core/activity_log/sanitize.py) before it enters amessage/metadatafield. Per-IP throttle bounds auth-failure audit writes. - Adding a new audited event: pick a dotted
action(e.g."thing.created"), call the recorder; for it to render localized in the UI, addactivity_log.msg.<action>to all threestatic/locales/*.json(the frontendlocalizeMessage()maps action→template; falls back to the servermessage). Entity-type labels live underactivity_log.entity_type.<type>. - Adding a new realtime event type (
pm.fire_event({"type": ...})): add it to_ALLOWED_SERVER_EVENT_TYPESinstatic/js/core/events-ws.tsAND keeptests/test_events_ws_parity.pygreen. - Retention + API.
core/activity_log/retention.pyprunes bymax_days+max_entries(settings persisted viadb.set_setting("activity_log")); the recorder'senabledflag is rehydrated from those settings on startup. REST inapi/routes/activity_log.py:GET /activity-log(list,AuthRequired),GET /export(CSV/JSON stream —require_authenticated; chunked keyset so it never holds the DB lock across the stream; CSV formula-injection guarded),GET|PUT /settings(PUT isrequire_authenticated),DELETE(clear —require_authenticated, self-audited). The table is covered by the existing whole-DB backup (noSTORE_MAPchange needed).
Common Tasks
Adding a new API endpoint
- Create route file in
api/routes/(define anAPIRouter(prefix="/api/v1/...")) - Define request/response schemas in
api/schemas/ - Register the router in
api/__init__.py(it aggregates every route module into the singlerouterthatmain.pymounts) - Restart the server
- Test via
/docs(Swagger UI)
Adding a new field to existing API
- Update Pydantic schema in
api/schemas/ - Update corresponding dataclass in
storage/ - Update backend logic to populate the field
- Restart the server
- Update frontend to display the new field
- Rebuild bundle:
cd server && npm run build
Adding translations
- Add keys to
static/locales/en.json,static/locales/ru.json, andstatic/locales/zh.json - Add
data-i18nattributes to HTML elements intemplates/ - Use
t('key')in TypeScript modules (static/js/) - No server restart needed (frontend only), but rebuild bundle if JS changed
Modifying display/monitor detection
- Update functions in
utils/monitor_names.pyorcore/screen_capture.py - Restart the server
- Test with
GET /api/v1/config/displays
Testing
cd server && pytest # Run all tests
cd server && pytest --cov # With coverage report
cd server && pytest tests/test_api.py # Single test file
Tests are in server/tests/. Config in pyproject.toml under [tool.pytest].
Frontend Conventions
For all frontend conventions (CSS variables, UI patterns, modals, localization, icons, bundling), see contexts/frontend.md.
Server Operations
For restart procedures, server modes, and demo mode checklist, see contexts/server-operations.md.