# Audit History This document explains how the Audit History feature works — what it tracks, where the data lives, how to query it, and how to extend it. --- ## What it does Every meaningful action a member takes in the Members area is recorded in the `audit_logs` database table. Application errors that happen while a logged-in member is using the site are also captured automatically. Admins can review all of this from the **Audit History** page under **Insights** in the admin area. --- ## What gets tracked ### Member actions (explicit) These are recorded directly inside the API endpoint that handles the action: | Action | Trigger | |---|---| | **Account claimed** | Member sets their password for the first time | | **Login** | Member completes 2FA and receives a session token | | **Onboarding updated** | Member saves or completes their onboarding details | | **Contract signed** | Member agrees to the service agreement | | **Profile updated** | Member changes their contact details | | **Booking created** | Member submits a booking request | | **Message read** | Member opens an admin message for the first time | | **Page visit** | Member navigates to any authenticated page | ### Application errors (automatic) Any unhandled exception that occurs while a member is making an authenticated API request is captured by a global exception handler in the FastAPI app. The error message and stack trace are stored in the audit record. This covers bugs, unexpected failures, and anything the normal error handling didn't anticipate. --- ## What each record contains | Field | Description | |---|---| | `timestamp` | When the event occurred (UTC) | | `member_id` | UUID of the member (nullable — set to NULL if member is deleted, log is kept) | | `member_email` | Email address at the time of the event (denormalised for readability) | | `action_type` | Short code identifying the action (e.g. `login`, `booking_created`, `error`) | | `area` | The page or API path where the action occurred (e.g. `members/book`) | | `description` | A plain-language sentence describing what happened | | `status` | `success`, `warning`, or `error` | | `booking_id` | UUID of the related booking, if applicable | | `error_message` | Short error message, for `error`-status records | | `error_detail` | Full stack trace, for `error`-status records | | `ip_address` | Client IP address at the time of the request | | `user_agent` | Browser/client user agent string | | `extra` | JSON blob for any additional context (e.g. `{"service_type": "pack_walk"}`) | --- ## How audit records survive deletions The `member_id` and `booking_id` fields use `ON DELETE SET NULL` foreign keys. If a member or booking is deleted from the database, those fields become `NULL` — but the audit record itself is **never deleted**. The `member_email` field preserves who the record belongs to even after the member is gone. --- ## The Admin UI Go to **Admin → Insights → Audit History**. **Filters available:** - Free-text search (matches email, description, area, action type, error message) - Member email (separate field for targeted filtering) - Action type (dropdown of all known action types) - Status (`success` / `warning` / `error`) - Area / page (partial match) - Date range (from / to) - Records per page (25, 50, 100, 200) **Sorting:** Click any column header. Click again to flip direction. Sortable columns: Timestamp, Member, Action, Area, Status. **Pagination:** Previous/next buttons, direct page buttons, and a summary showing which records are visible. **Detail panel:** Click the arrow on any row to expand full details — complete timestamp, member ID, booking ID, IP address, user agent, extra JSON data, and full stack trace for errors. --- ## Database **Table:** `audit_logs` **Indexes:** - `ix_audit_logs_timestamp` — for date-range queries and default sort - `ix_audit_logs_member_id` — for filtering by member - `ix_audit_logs_action_type` — for filtering by action - `ix_audit_logs_status` — for filtering by status **Migration file:** `backend/alembic/versions/b3e7c9a2f1d4_add_audit_logs.py` Run the migration with: ``` alembic upgrade head ``` --- ## How to add audit logging to a new endpoint Import and call `log_audit` from within any async route handler that already has a database session: ```python from app.services.audit import log_audit await log_audit( db, member_id=member.id, member_email=member.email, action_type="my_action_type", area="members/my-page", description="Member did the thing.", status="success", # optional: booking_id=booking.id, ip_address=request.client.host if request.client else None, user_agent=request.headers.get("User-Agent"), extra={"any": "extra context"}, ) ``` The entry is added to the session and committed with the rest of the request transaction. No separate commit is needed. For error logging outside a normal request (e.g. background tasks), open a fresh session: ```python async with AsyncSessionLocal() as session: await log_audit(session, ...) await session.commit() ``` --- ## Files | File | Purpose | |---|---| | `backend/app/models/audit.py` | `AuditLog` SQLAlchemy model | | `backend/app/services/audit.py` | `log_audit()` helper function | | `backend/app/schemas/audit.py` | Pydantic request/response schemas | | `backend/app/routers/audit.py` | Admin query endpoint + member page-visit endpoint | | `backend/alembic/versions/b3e7c9a2f1d4_add_audit_logs.py` | Database migration | | `frontend/src/routes/admin/audit/+page.svelte` | Admin Audit History UI | | `frontend/src/lib/memberApi.js` | `logPageVisit()` client helper | | `frontend/src/routes/members/+layout.svelte` | Calls `logPageVisit` on every page change |