Files
motovaultpro/docs/DATABASE-SCHEMA.md
Eric Gullickson 3053b62fa5
All checks were successful
Deploy to Staging / Build Images (push) Successful in 2m19s
Deploy to Staging / Deploy to Staging (push) Successful in 27s
Deploy to Staging / Verify Staging (push) Successful in 5s
Deploy to Staging / Notify Staging Ready (push) Successful in 5s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Mirror Base Images / Mirror Base Images (push) Successful in 29s
chore: Update Documentation
2026-01-03 15:10:19 -06:00

15 KiB

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.

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_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

# 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