Files
gw/audit-history.md
ponzischeme89 6d44e05de4 v1
2026-04-18 07:23:55 +12:00

5.7 KiB

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:

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:

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