Create a dedicated "Admin Logs" section in the admin interface to centralize all audit logging. Currently, an audit logs box exists in the "Vehicle Catalog" section but CRUD operations are not being logged properly.
Requirements
New Admin Section
Add "Admin Logs" as a new section alongside "Vehicle Catalog", "User Management", and "Backup & Restore"
Remove the existing audit logs box from the "Vehicle Catalog" section
Centralize all audit log viewing in the new section
This plan implements a centralized audit logging system by creating a new unified audit_logs table with category, severity, and full-text search support. The existing admin_audit_logs and platform_change_log tables will remain as-is (no backfill required). A new Admin Logs section will provide unified viewing with search, filters, date range picker, and CSV export. A scheduled cleanup job will enforce 90-day retention.
Approach: New Unified Table (Option A-Prime) - cleanest schema, no migration complexity, single source of truth for all new audit events.
Planning Context
Decision Log
Decision
Reasoning Chain
New audit_logs table
Two existing tables have incompatible schemas (actor/target vs old/new value) -> modifying both adds migration risk -> new unified table provides clean design with all required fields (category, severity, search) -> no data loss since existing tables remain
Current useAuditLogStream does client-side filtering -> fetches all then filters locally -> won't scale with log volume -> server-side WHERE clauses with pagination more efficient
GIN index on action
Free text search required -> PostgreSQL GIN index on action column with gin_trgm_ops -> enables fast ILIKE queries -> standard pattern for search
Filtered results could be large -> streaming response prevents memory exhaustion -> Content-Disposition header triggers browser download -> standard pattern
Feature capsule organization
New audit-log feature under backend/src/features/ -> matches existing pattern (admin, vehicles, backup) -> self-contained with api/domain/data/migrations
AuditLogService as singleton
Multiple features need logging -> service registered in Fastify DI container -> accessed via fastify.auditLog.log() -> consistent with CacheService pattern
Rejected Alternatives
Alternative
Why Rejected
SQL View over existing tables
UNION query performance overhead + need to add columns to both tables anyway + complex column mapping for different schemas
Application-level aggregation
Pagination across two data sources is complex + inconsistent sorting + memory pressure from loading two datasets
Event sourcing pattern
Over-engineered for single-tenant app + unnecessary complexity for audit trail requirements + no replay needed
Backfill existing data
Historical data minimal in new app + adds migration risk + new unified logging starts fresh is acceptable
WebSocket real-time streaming
Polling every 5s sufficient for admin monitoring use case + existing pattern in codebase + simpler architecture
After comprehensive analysis using codebase exploration, problem analysis, and decision critique:
Finding: The GET /api/auth/user-status endpoint is only called from callback pages (CallbackPage.tsx:13, CallbackMobileScreen.tsx:14) after Auth0 redirect - NOT on page refresh. This makes it safe for login tracking with zero frontend changes.
Decision: Modify getUserStatus() for login tracking + create new /auth/track-logout endpoint for logout tracking.
## Implementation Complete: Login/Logout Audit Logging
### Changes Made
**Backend** (`fbde51b`):
- Added login event logging to `getUserStatus()` in `auth.controller.ts`
- Created `POST /auth/track-logout` endpoint for logout tracking
- Both events use `auditLogService.info('auth', ...)` pattern
**Frontend**:
- Created `useLogout` hook (`frontend/src/core/auth/useLogout.ts`) that wraps Auth0 logout with audit tracking
- Updated all logout locations to use the new hook:
- `SettingsPage.tsx` (desktop)
- `Layout.tsx` (sidebar)
- `MobileSettingsScreen.tsx` (mobile)
- `useDeletion.ts` (account deletion flow)
### How It Works
**Login tracking**:
- Frontend calls `/api/auth/user-status` after Auth0 callback
- Backend logs: `'User login'` with IP address
**Logout tracking**:
- `useLogout` hook calls `/api/auth/track-logout` (fire-and-forget)
- Then performs Auth0 logout
- Backend logs: `'User logout'` with IP address
### Verification
- TypeScript: Compiles successfully (frontend + backend)
- ESLint: Only pre-existing warnings (no new issues)
- Pattern: Matches existing auth logging (signup, password-reset)
---
Ready for testing on staging.
Fix: Display user email instead of Auth0 UID in audit logs
The "User" column in the Admin Logs display was showing the raw Auth0 UID (e.g., auth0|123456...) instead of a human-readable email address.
Changes Made
Backend:
Added userEmail field to AuditLogEntry type
Updated audit-log repository to LEFT JOIN with user_profiles table to fetch user email based on Auth0 sub
Frontend:
Added userEmail to UnifiedAuditLog type
Updated AdminLogsPage.tsx to display:
User email when available
Truncated Auth0 UID as fallback (for users not in profiles)
"System" for system-generated logs (null userId)
Applied same logic to AdminLogsMobileScreen.tsx
Commit
911b7c0 - fix: Display user email instead of Auth0 UID in audit logs (refs #10)
## Fix: Display user email instead of Auth0 UID in audit logs
The "User" column in the Admin Logs display was showing the raw Auth0 UID (e.g., `auth0|123456...`) instead of a human-readable email address.
### Changes Made
**Backend:**
- Added `userEmail` field to `AuditLogEntry` type
- Updated audit-log repository to `LEFT JOIN` with `user_profiles` table to fetch user email based on Auth0 sub
**Frontend:**
- Added `userEmail` to `UnifiedAuditLog` type
- Updated `AdminLogsPage.tsx` to display:
- User email when available
- Truncated Auth0 UID as fallback (for users not in profiles)
- "System" for system-generated logs (null userId)
- Applied same logic to `AdminLogsMobileScreen.tsx`
### Commit
`911b7c0` - fix: Display user email instead of Auth0 UID in audit logs (refs #10)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Create a dedicated "Admin Logs" section in the admin interface to centralize all audit logging. Currently, an audit logs box exists in the "Vehicle Catalog" section but CRUD operations are not being logged properly.
Requirements
New Admin Section
Log Categories
authvehicleusersystemadminSeverity Levels
info- Normal operationswarning- Potential issueserror- Failed operationsDisplay Columns
Search & Filtering
Export
Retention Policy
Technical Considerations
Backend
Frontend
Acceptance Criteria
Plan: Centralized Audit Logging Admin Interface
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Overview
This plan implements a centralized audit logging system by creating a new unified
audit_logstable with category, severity, and full-text search support. The existingadmin_audit_logsandplatform_change_logtables will remain as-is (no backfill required). A new Admin Logs section will provide unified viewing with search, filters, date range picker, and CSV export. A scheduled cleanup job will enforce 90-day retention.Approach: New Unified Table (Option A-Prime) - cleanest schema, no migration complexity, single source of truth for all new audit events.
Planning Context
Decision Log
audit_logstableuseAuditLogStreamdoes client-side filtering -> fetches all then filters locally -> won't scale with log volume -> server-side WHERE clauses with pagination more efficientactioncolumn withgin_trgm_ops-> enables fast ILIKE queries -> standard pattern for searchDELETE WHERE created_at < NOW() - INTERVAL '90 days'-> simple, reliable, no external dependenciesaudit-logfeature underbackend/src/features/-> matches existing pattern (admin, vehicles, backup) -> self-contained with api/domain/data/migrationsfastify.auditLog.log()-> consistent with CacheService patternRejected Alternatives
Constraints & Assumptions
Known Risks
Invisible Knowledge
Architecture
Data Flow
Why This Structure
Invariants
Milestones
Milestone 1: Database Schema
Files:
backend/src/features/audit-log/migrations/001_create_audit_logs.sqlRequirements:
audit_logstable with columns: id (UUID), category, severity, user_id, action, resource_type, resource_id, details (JSONB), created_atAcceptance Criteria:
Tests:
backend/src/features/audit-log/__tests__/migrations.test.tsMilestone 2: Backend Core (Types, Repository, Service)
Files:
backend/src/features/audit-log/domain/audit-log.types.tsbackend/src/features/audit-log/data/audit-log.repository.tsbackend/src/features/audit-log/domain/audit-log.service.tsbackend/src/features/audit-log/index.tsRequirements:
AuditLogCategory,AuditLogSeverity,AuditLogEntry,AuditLogFiltersAuditLogRepositorywith:create(),search(filters, pagination),exportCsv(filters),cleanup(olderThanDays)AuditLogServicewith:log(category, severity, userId, action, resourceType?, resourceId?, details?)Acceptance Criteria:
service.log()creates entry in databaserepository.search()returns filtered, paginated resultsrepository.cleanup(90)deletes entries older than 90 daysTests:
backend/src/features/audit-log/__tests__/audit-log.service.test.tsMilestone 3: Backend API Routes
Files:
backend/src/features/audit-log/api/audit-log.controller.tsbackend/src/features/audit-log/api/audit-log.routes.tsRequirements:
GET /api/admin/audit-logswith query params: search, category, severity, startDate, endDate, limit, offsetGET /api/admin/audit-logs/exportreturns CSV with Content-Disposition headerrequireAdminpreHandlerAcceptance Criteria:
Tests:
backend/src/features/audit-log/__tests__/audit-log.routes.test.tsMilestone 4: Backend Wiring (Add Logging to Features)
Files:
backend/src/features/auth/api/auth.routes.ts(add login/logout logging)backend/src/features/vehicles/domain/vehicles.service.ts(add CRUD logging)backend/src/features/admin/domain/admin.service.ts(update to use new service)backend/src/features/backup/domain/backup.service.ts(add backup/restore logging)Requirements:
Acceptance Criteria:
Tests:
backend/src/features/audit-log/__tests__/audit-log.integration.test.tsMilestone 5: Frontend AdminLogsPage (Desktop)
Files:
frontend/src/pages/admin/AdminLogsPage.tsxfrontend/src/features/admin/hooks/useAuditLogs.tsfrontend/src/features/admin/api/admin.api.ts(add getAuditLogs, exportAuditLogs)frontend/src/App.tsx(add route)Requirements:
/garage/settings/admin/logsAcceptance Criteria:
Tests:
frontend/src/features/admin/__tests__/AdminLogsPage.test.tsxMilestone 6: Frontend Mobile Screen
Files:
frontend/src/features/admin/mobile/AdminLogsMobileScreen.tsxfrontend/src/App.tsx(add mobile screen case)Requirements:
Acceptance Criteria:
Tests:
frontend/src/features/admin/__tests__/AdminLogsMobileScreen.test.tsxMilestone 7: Cleanup and Retention
Files:
backend/src/features/audit-log/jobs/cleanup.job.tsfrontend/src/pages/admin/AdminCatalogPage.tsx(remove AuditLogPanel)backend/src/features/admin/CLAUDE.md(update)backend/src/features/audit-log/CLAUDE.md(create)backend/src/features/audit-log/README.md(create with architecture)Requirements:
Acceptance Criteria:
Tests:
backend/src/features/audit-log/__tests__/cleanup.job.test.tsMilestone Dependencies
Verdict: AWAITING_REVIEW | Next: QR plan-completeness review
Milestone 1: Database Schema - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
backend/src/features/audit-log/migrations/001_create_audit_logs.sqlbackend/src/features/audit-log/__tests__/migrations.test.tsSchema Details
Indexes Created
idx_audit_logs_category_created- B-tree for category filteringidx_audit_logs_severity_created- B-tree for severity filteringidx_audit_logs_user_created- B-tree for user filteringidx_audit_logs_created- B-tree for date orderingidx_audit_logs_action_gin- GIN trigram for text searchTests
Next: Milestone 2 - Backend Core (Types, Repository, Service)
Milestone 2: Backend Core - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
backend/src/features/audit-log/domain/audit-log.types.tsbackend/src/features/audit-log/data/audit-log.repository.tsbackend/src/features/audit-log/domain/audit-log.service.tsbackend/src/features/audit-log/index.tsbackend/src/features/audit-log/__tests__/audit-log.service.test.tsAPI Surface
Types:
AuditLogCategory: 'auth' | 'vehicle' | 'user' | 'system' | 'admin'AuditLogSeverity: 'info' | 'warning' | 'error'AuditLogEntry: Full log entry with all fieldsAuditLogFilters: Search, category, severity, date rangeAuditLogSearchResult: Paginated results with totalAuditLogService Methods:
log(category, severity, userId, action, resourceType?, resourceId?, details?)- Create log entryinfo/warning/error()- Convenience methodssearch(filters, pagination)- Search with filtersgetForExport(filters)- Get all matching for CSVcleanup(days)- Delete old entriesAuditLogRepository Methods:
create()- Insert log entrysearch()- Filtered paginated querygetForExport()- All matching entries (max 10k)cleanup()- DELETE older than N daysTests
Next: Milestone 3 - Backend API Routes
Milestone 3: Backend API Routes - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
backend/src/features/audit-log/api/audit-log.controller.tsbackend/src/features/audit-log/api/audit-log.routes.tsbackend/src/app.tsbackend/src/features/audit-log/__tests__/audit-log.routes.test.tsAPI Endpoints
GET /api/admin/audit-logs
{ logs: AuditLogEntry[], total: number, limit: number, offset: number }GET /api/admin/audit-logs/export
Features
Tests
Next: Milestone 4 - Backend Wiring (Add logging to features)
Milestone 4: Backend Wiring - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
backend/src/features/audit-log/audit-log.instance.tsbackend/src/features/audit-log/index.tsbackend/src/features/auth/api/auth.controller.tsbackend/src/features/vehicles/domain/vehicles.service.tsbackend/src/features/admin/domain/admin.service.tsbackend/src/features/backup/api/backup.controller.tsbackend/src/features/audit-log/__tests__/audit-log.integration.test.tsbackend/src/features/audit-log/__tests__/*.test.tsAudit Logging Wiring
Auth (category='auth'):
Vehicle (category='vehicle'):
Admin (category='admin'):
System/Backup (category='system'):
Implementation Pattern
All audit logging uses non-blocking
.catch()pattern to prevent audit failures from breaking main operations:Quality Checks
Next: Milestone 5 - Frontend AdminLogsPage (Desktop)
Milestone 5: Frontend AdminLogsPage (Desktop) - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
frontend/src/features/admin/types/admin.types.tsfrontend/src/features/admin/api/admin.api.tsfrontend/src/features/admin/hooks/useAuditLogs.tsfrontend/src/pages/admin/AdminLogsPage.tsxfrontend/src/App.tsxFeatures
Types Added:
AuditLogCategory: 'auth' | 'vehicle' | 'user' | 'system' | 'admin'AuditLogSeverity: 'info' | 'warning' | 'error'UnifiedAuditLog: Full log entry interfaceUnifiedAuditLogsResponse: Paginated responseAuditLogFilters: Search and filter paramsAPI Methods:
adminApi.unifiedAuditLogs.list(filters): Fetch paginated logsadminApi.unifiedAuditLogs.export(filters): Export as CSVDesktop Page Features:
Route:
/garage/settings/admin/logsMilestone 6: Frontend Mobile Screen - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
frontend/src/features/admin/mobile/AdminLogsMobileScreen.tsxfrontend/src/core/store/navigation.tsfrontend/src/App.tsxMobile Features
Navigation: Accessible via hamburger menu as "AdminLogs" screen
Quality Checks
Next: Milestone 7 - Cleanup and Retention
Milestone 7: Cleanup and Retention - COMPLETE
Phase: Execution | Agent: Developer | Status: COMPLETE
Deliverables
backend/src/features/audit-log/jobs/cleanup.job.tsbackend/src/core/scheduler/index.tsfrontend/src/pages/admin/AdminCatalogPage.tsxbackend/src/features/audit-log/CLAUDE.mdbackend/src/features/audit-log/README.mdbackend/src/features/CLAUDE.mdCleanup Job
Schedule: Daily at 3 AM
Retention: 90 days
Implementation: Uses existing scheduler infrastructure with node-cron
Documentation
CLAUDE.md includes:
README.md includes:
Quality Checks
Issue #10 Implementation Complete
All 7 milestones have been completed:
Summary of Deliverables
Backend:
audit_logstable with proper indexesAuditLogServiceandAuditLogRepositoryFrontend:
Ready for testing and PR creation.
Implementation Plan: Login/Logout Audit Logging
Analysis Summary
After comprehensive analysis using codebase exploration, problem analysis, and decision critique:
Finding: The
GET /api/auth/user-statusendpoint is only called from callback pages (CallbackPage.tsx:13,CallbackMobileScreen.tsx:14) after Auth0 redirect - NOT on page refresh. This makes it safe for login tracking with zero frontend changes.Decision: Modify
getUserStatus()for login tracking + create new/auth/track-logoutendpoint for logout tracking.Implementation Plan
1. Login Tracking (Backend Only)
File:
backend/src/features/auth/api/auth.controller.tsAdd audit logging to
getUserStatus():2. Logout Tracking (Backend + Frontend)
Backend:
POST /auth/track-logouttoauth.routes.tstrackLogout()handler toauth.controller.tsFrontend:
Auth0Provider.tsxlogout handler to call/api/auth/track-logoutbefore Auth0 logout (fire-and-forget)Files to Modify
backend/src/features/auth/api/auth.controller.tsgetUserStatus(), addtrackLogout()backend/src/features/auth/api/auth.routes.ts/auth/track-logoutroutefrontend/src/core/auth/Auth0Provider.tsxbackend/src/features/auth/__tests__/auth.integration.test.tsVerification Findings
Starting implementation now.
Implementation Complete: Login/Logout Audit Logging
Changes Made
Backend (
fbde51b):getUserStatus()inauth.controller.tsPOST /auth/track-logoutendpoint for logout trackingauditLogService.info('auth', ...)patternFrontend:
useLogouthook (frontend/src/core/auth/useLogout.ts) that wraps Auth0 logout with audit trackingSettingsPage.tsx(desktop)Layout.tsx(sidebar)MobileSettingsScreen.tsx(mobile)useDeletion.ts(account deletion flow)How It Works
Login tracking:
/api/auth/user-statusafter Auth0 callback'User login'with IP addressLogout tracking:
useLogouthook calls/api/auth/track-logout(fire-and-forget)'User logout'with IP addressVerification
Ready for testing on staging.
Fix: Display user email instead of Auth0 UID in audit logs
The "User" column in the Admin Logs display was showing the raw Auth0 UID (e.g.,
auth0|123456...) instead of a human-readable email address.Changes Made
Backend:
userEmailfield toAuditLogEntrytypeLEFT JOINwithuser_profilestable to fetch user email based on Auth0 subFrontend:
userEmailtoUnifiedAuditLogtypeAdminLogsPage.tsxto display:AdminLogsMobileScreen.tsxCommit
911b7c0- fix: Display user email instead of Auth0 UID in audit logs (refs #10)