# Database Schema Overview

Complete database schema for MotoVaultPro 5-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`
