Files
motovaultpro/docs/DATABASE-SCHEMA.md
Eric Gullickson 8331bde4b0
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 34s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 51s
Deploy to Staging / Verify Staging (pull_request) Successful in 2m37s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
docs: update 5-container refs to 9-container architecture (refs #101)
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>
2026-02-05 20:11:31 -06:00

562 lines
15 KiB
Markdown

# Database Schema Overview
Complete database schema for MotoVaultPro 9-container architecture with 15 feature capsules.
## Migration System
### Migration Order (Dependencies)
1. **admin** - Admin users and audit logs, no dependencies
2. **user-profile** - User profiles, no dependencies
3. **user-preferences** - User preferences, depends on user-profile
4. **terms-agreement** - Terms acceptance audit, no dependencies
5. **platform** - Vehicle lookup data (engines, transmissions, vehicle_options)
6. **vehicles** - Primary vehicle entity, no dependencies
7. **fuel-logs** - Depends on vehicles (vehicle_id FK)
8. **maintenance** - Depends on vehicles (vehicle_id FK)
9. **stations** - Independent feature (includes community_stations)
10. **documents** - Depends on vehicles (vehicle_id FK)
11. **notifications** - Email templates and logs, depends on maintenance/documents
12. **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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.).
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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.
```sql
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_id` column
- 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
```bash
# 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`