Update all documentation to reflect the current 9-container architecture (6 application + 3 logging) after the logging stack upgrades. Add missing OCR, Loki, Alloy, and Grafana services to context.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15 KiB
Database Schema Overview
Complete database schema for MotoVaultPro 9-container architecture with 15 feature capsules.
Migration System
Migration Order (Dependencies)
- admin - Admin users and audit logs, no dependencies
- user-profile - User profiles, no dependencies
- user-preferences - User preferences, depends on user-profile
- terms-agreement - Terms acceptance audit, no dependencies
- platform - Vehicle lookup data (engines, transmissions, vehicle_options)
- vehicles - Primary vehicle entity, no dependencies
- fuel-logs - Depends on vehicles (vehicle_id FK)
- maintenance - Depends on vehicles (vehicle_id FK)
- stations - Independent feature (includes community_stations)
- documents - Depends on vehicles (vehicle_id FK)
- notifications - Email templates and logs, depends on maintenance/documents
- backup - Backup schedules, history, settings
Features without migrations: auth, onboarding, user-export
Migration Tracking
- Table:
_migrations - Purpose: Tracks executed migration files to prevent re-execution
- Behavior: Migration system is idempotent at the file level - will skip already executed files
- Safety: Safe to re-run the migration system; unsafe to manually re-run individual SQL files
Admin Tables
admin_users
Admin role-based access control.
admin_users (
auth0_sub VARCHAR(255) PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
role VARCHAR(50) NOT NULL DEFAULT 'admin',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255) NOT NULL,
revoked_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Indexes: email, created_at, revoked_at Triggers: auto-update updated_at
admin_audit_logs
Audit trail for admin actions.
admin_audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_admin_id VARCHAR(255) NOT NULL,
target_admin_id VARCHAR(255),
action VARCHAR(100) NOT NULL,
resource_type VARCHAR(100),
resource_id VARCHAR(255),
context JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Indexes: actor_admin_id, target_admin_id, action, created_at
User Tables
user_profiles
User profile information with Auth0 integration.
user_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
auth0_sub VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL,
display_name VARCHAR(100),
notification_email VARCHAR(255),
email_verified BOOLEAN DEFAULT false,
onboarding_completed_at TIMESTAMP WITH TIME ZONE,
subscription_tier VARCHAR(50) DEFAULT 'free',
deactivated_at TIMESTAMP WITH TIME ZONE,
deletion_requested_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Indexes: auth0_sub Triggers: auto-update updated_at
user_preferences
User preference settings.
user_preferences (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL UNIQUE,
unit_system VARCHAR(20) DEFAULT 'imperial',
currency_code VARCHAR(3) DEFAULT 'USD',
time_zone VARCHAR(100) DEFAULT 'America/New_York',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Indexes: user_id
terms_agreements
Legal audit trail for T&C acceptance.
terms_agreements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(255) NOT NULL,
agreed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45) NOT NULL,
user_agent TEXT NOT NULL,
terms_version VARCHAR(50) NOT NULL,
terms_url VARCHAR(255) NOT NULL,
terms_content_hash VARCHAR(64) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Indexes: user_id Invariant: One record per user, created at signup
Vehicle Tables
vehicles
Primary entity for all vehicle data.
vehicles (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
vin VARCHAR(17),
make VARCHAR(100),
model VARCHAR(100),
year INTEGER,
trim VARCHAR(100),
engine VARCHAR(100),
transmission VARCHAR(100),
nickname VARCHAR(100),
color VARCHAR(50),
license_plate VARCHAR(20),
odometer_reading INTEGER DEFAULT 0,
image_bucket VARCHAR(128),
image_key VARCHAR(512),
is_active BOOLEAN DEFAULT true,
deleted_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
)
Indexes: user_id, vin, is_active, created_at Triggers: auto-update updated_at Constraints: VIN nullable (license plate can be used instead)
fuel_logs
Tracks fuel purchases and efficiency metrics.
fuel_logs (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE,
date_time TIMESTAMP WITH TIME ZONE NOT NULL,
odometer NUMERIC,
trip_distance NUMERIC,
fuel_type VARCHAR(50),
fuel_grade VARCHAR(50),
fuel_units DECIMAL(10,3) NOT NULL,
cost_per_unit DECIMAL(10,3) NOT NULL,
total_cost DECIMAL(10,2),
location_data VARCHAR(500),
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
)
Foreign Keys: vehicle_id -> vehicles.id (ON DELETE CASCADE) Indexes: user_id, vehicle_id, date_time, created_at
maintenance_logs
Completed maintenance records.
maintenance_logs (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE,
category VARCHAR(50) NOT NULL,
subtypes TEXT[],
service_date DATE NOT NULL,
odometer INTEGER,
cost DECIMAL(10,2),
shop_name VARCHAR(200),
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
)
Foreign Keys: vehicle_id -> vehicles.id (ON DELETE CASCADE) Indexes: user_id, vehicle_id, service_date
maintenance_schedules
Recurring maintenance schedules with notification settings.
maintenance_schedules (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE,
category VARCHAR(50) NOT NULL,
subtypes TEXT[],
interval_months INTEGER,
interval_miles INTEGER,
last_service_date DATE,
last_service_mileage INTEGER,
next_due_date DATE,
next_due_mileage INTEGER,
email_notifications BOOLEAN DEFAULT false,
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
)
Foreign Keys: vehicle_id -> vehicles.id (ON DELETE CASCADE) Indexes: user_id, vehicle_id, next_due_date
documents
Vehicle document storage (insurance, registration, etc.).
documents (
id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE,
document_type VARCHAR(32) NOT NULL CHECK (document_type IN ('insurance','registration','manual')),
title VARCHAR(200) NOT NULL,
notes TEXT,
details JSONB,
storage_bucket VARCHAR(128),
storage_key VARCHAR(512),
file_name VARCHAR(255),
content_type VARCHAR(128),
file_size BIGINT,
file_hash VARCHAR(128),
issued_date DATE,
expiration_date DATE,
email_notifications BOOLEAN DEFAULT false,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITHOUT TIME ZONE
)
Foreign Keys: vehicle_id -> vehicles.id (ON DELETE CASCADE) Indexes: user_id, vehicle_id, document_type, expiration_date
Station Tables
stations
Gas station locations from Google Maps.
stations (
id UUID PRIMARY KEY,
google_place_id VARCHAR(255) UNIQUE,
name VARCHAR(200) NOT NULL,
address TEXT,
latitude DECIMAL(10,8),
longitude DECIMAL(11,8),
phone VARCHAR(20),
website VARCHAR(255),
has_93_octane BOOLEAN,
photo_reference VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
)
External Source: Google Maps Places API Cache Strategy: Postgres-based cache with TTL management
community_stations
User-reported station information.
community_stations (
id UUID PRIMARY KEY,
station_id UUID REFERENCES stations(id),
user_id VARCHAR(255) NOT NULL,
has_93_octane BOOLEAN,
reported_at TIMESTAMP WITH TIME ZONE,
removed_at TIMESTAMP WITH TIME ZONE,
removal_reason VARCHAR(100)
)
Foreign Keys: station_id -> stations.id Purpose: Track user reports of 93 octane availability
Platform Tables
engines
Engine specifications for vehicle catalog.
engines (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
displacement VARCHAR(50),
configuration VARCHAR(50),
horsepower VARCHAR(100),
torque VARCHAR(100),
fuel_type VARCHAR(100),
fuel_system VARCHAR(255),
aspiration VARCHAR(100),
specs_json JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
Unique Index: LOWER(name) - case-insensitive uniqueness Indexes: displacement, configuration
transmissions
Transmission specifications for vehicle catalog.
transmissions (
id SERIAL PRIMARY KEY,
type VARCHAR(100) NOT NULL,
speeds VARCHAR(50),
drive_type VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
Unique Index: LOWER(type) - case-insensitive uniqueness
vehicle_options
Denormalized vehicle lookup data for dropdown cascades.
vehicle_options (
id SERIAL PRIMARY KEY,
year INTEGER NOT NULL,
make VARCHAR(100) NOT NULL,
model VARCHAR(255) NOT NULL,
trim VARCHAR(255) NOT NULL,
engine_id INTEGER REFERENCES engines(id) ON DELETE SET NULL,
transmission_id INTEGER REFERENCES transmissions(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
Foreign Keys: engine_id -> engines.id, transmission_id -> transmissions.id Unique Index: (year, make, model, trim, engine_id, transmission_id) Indexes: Optimized for cascade queries (year, year+make, year+make+model, etc.)
Notification Tables
email_templates
Admin-editable email templates.
email_templates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
template_key VARCHAR(50) NOT NULL UNIQUE CHECK (template_key IN (
'maintenance_due_soon', 'maintenance_overdue',
'document_expiring', 'document_expired'
)),
name VARCHAR(100) NOT NULL,
description TEXT,
subject VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
variables JSONB DEFAULT '[]'::jsonb,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Seeded Templates: 4 predefined templates for maintenance and document notifications
notification_logs
Track sent email notifications.
notification_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(255) NOT NULL,
notification_type VARCHAR(20) NOT NULL CHECK (notification_type IN ('email', 'toast')),
template_key VARCHAR(50) NOT NULL,
recipient_email VARCHAR(255),
subject VARCHAR(255),
reference_type VARCHAR(50),
reference_id UUID,
status VARCHAR(20) DEFAULT 'sent' CHECK (status IN ('pending', 'sent', 'failed')),
error_message TEXT,
sent_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
Indexes: user_id, reference_type+reference_id, sent_at
Backup Tables
backup_schedules
Scheduled backup configurations.
backup_schedules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
frequency VARCHAR(20) NOT NULL CHECK (frequency IN ('hourly', 'daily', 'weekly', 'monthly')),
cron_expression VARCHAR(50) NOT NULL,
retention_count INTEGER NOT NULL DEFAULT 7,
is_enabled BOOLEAN DEFAULT true,
last_run_at TIMESTAMP WITH TIME ZONE,
next_run_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
)
Indexes: is_enabled, next_run_at
backup_history
Record of all backup operations.
backup_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
schedule_id UUID REFERENCES backup_schedules(id) ON DELETE SET NULL,
backup_type VARCHAR(20) NOT NULL CHECK (backup_type IN ('scheduled', 'manual')),
filename VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size_bytes BIGINT NOT NULL,
database_tables_count INTEGER,
documents_count INTEGER,
status VARCHAR(20) NOT NULL CHECK (status IN ('in_progress', 'completed', 'failed')),
error_message TEXT,
started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE,
created_by VARCHAR(255),
metadata JSONB DEFAULT '{}'
)
Foreign Keys: schedule_id -> backup_schedules.id (ON DELETE SET NULL) Indexes: status, started_at, schedule_id
backup_settings
Global backup configuration.
backup_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
setting_key VARCHAR(50) UNIQUE NOT NULL,
setting_value TEXT NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
)
Default Settings: email_on_success, email_on_failure, admin_email, max_backup_size_mb, compression_enabled
Relationships
user_profiles (1) ---- (1) user_preferences
|
+---- (1) terms_agreements
vehicles (1) ---- (many) fuel_logs
|
+---- (many) maintenance_logs
|
+---- (many) maintenance_schedules
|
+---- (many) documents
stations (1) ---- (many) community_stations
vehicle_options (many) ---- (1) engines
|
+---- (1) transmissions
backup_schedules (1) ---- (many) backup_history
Data Constraints
User Data Isolation
- All user data tables include
user_idcolumn - Application enforces user-scoped queries
- No cross-user data access possible
Referential Integrity
- fuel_logs, maintenance_logs, maintenance_schedules, documents -> vehicles.id (ON DELETE CASCADE)
- community_stations -> stations.id
- vehicle_options -> engines.id, transmissions.id (ON DELETE SET NULL)
- backup_history -> backup_schedules.id (ON DELETE SET NULL)
VIN Validation
- 17 characters when provided (now optional)
- Cannot contain letters I, O, or Q
- Application-level checksum validation
- Either VIN or license_plate required
Caching Strategy
Application-Level Caching (Redis)
- Platform dropdown data: 6 hours (key:
dropdown:{dataType}:{params}) - VIN decodes: 7 days (key:
vin:decode:{vin}) - User vehicle lists: 5 minutes (key:
vehicles:user:{userId}) - Fuel logs per vehicle: 5 minutes (key:
fuel-logs:vehicle:{vehicleId}:{unitSystem}) - Maintenance data: Unit system-aware caching where applicable
Migration Commands
Run All Migrations
# In container
npm run migrate:all
# Via Docker
make migrate
Migration Files
- Location:
backend/src/features/[feature]/migrations/ - Format:
001_descriptive_name.sql - Order: Lexicographic sorting (001, 002, etc.)
Database Connection
Development (Docker)
- Host: mvp-postgres (container name)
- Port: 5432
- Database: motovaultpro
- User: postgres
- Password: Loaded from secrets file
/run/secrets/postgres-password
Connection Pool
- Implementation: pg (node-postgres)
- Pool Size: Default (10 connections)
- Idle Timeout: 30 seconds
- Location:
backend/src/core/config/database.ts