chore: Update Documentation
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

This commit is contained in:
Eric Gullickson
2026-01-03 15:10:19 -06:00
parent 485bfd3dfc
commit 3053b62fa5
8 changed files with 1149 additions and 273 deletions

View File

@@ -115,12 +115,29 @@
"description": "Admin role management, platform catalog CRUD, station oversight", "description": "Admin role management, platform catalog CRUD, station oversight",
"status": "implemented" "status": "implemented"
}, },
"vehicles": { "auth": {
"path": "backend/src/features/vehicles/", "path": "backend/src/features/auth/",
"type": "core_feature", "type": "core_feature",
"self_contained": true, "self_contained": true,
"database_tables": ["vehicles"], "database_tables": [],
"cache_strategy": "User vehicle lists: 5 minutes", "description": "User signup, email verification workflow using Auth0",
"status": "implemented"
},
"backup": {
"path": "backend/src/features/backup/",
"type": "admin_feature",
"self_contained": true,
"database_tables": ["backup_schedules", "backup_history", "backup_settings"],
"storage": "/app/data/backups/",
"description": "Manual and scheduled database/document backups with retention policies",
"status": "implemented"
},
"documents": {
"path": "backend/src/features/documents/",
"type": "independent_feature",
"self_contained": true,
"database_tables": ["documents"],
"storage": "/app/data/documents/",
"status": "implemented" "status": "implemented"
}, },
"fuel-logs": { "fuel-logs": {
@@ -141,21 +158,23 @@
"cache_strategy": "Upcoming maintenance: 1 hour", "cache_strategy": "Upcoming maintenance: 1 hour",
"status": "implemented" "status": "implemented"
}, },
"stations": { "notifications": {
"path": "backend/src/features/stations/", "path": "backend/src/features/notifications/",
"type": "independent_feature", "type": "dependent_feature",
"self_contained": true, "self_contained": true,
"external_apis": ["Google Maps API"], "depends_on": ["maintenance", "documents"],
"database_tables": ["stations", "community_stations"], "database_tables": ["email_templates", "notification_logs", "sent_notification_tracker", "user_notifications"],
"cache_strategy": "Station searches: 1 hour", "external_apis": ["Resend"],
"description": "Email and toast notifications for maintenance due/overdue and expiring documents",
"status": "implemented" "status": "implemented"
}, },
"documents": { "onboarding": {
"path": "backend/src/features/documents/", "path": "backend/src/features/onboarding/",
"type": "independent_feature", "type": "dependent_feature",
"self_contained": true, "self_contained": true,
"database_tables": ["documents"], "depends_on": ["user-profile", "user-preferences"],
"storage": "/app/data/documents/", "database_tables": [],
"description": "User onboarding flow after email verification (preferences, first vehicle)",
"status": "implemented" "status": "implemented"
}, },
"platform": { "platform": {
@@ -166,11 +185,61 @@
"cache_strategy": "Vehicle hierarchical data: 6 hours", "cache_strategy": "Vehicle hierarchical data: 6 hours",
"description": "Vehicle hierarchical data lookups (years, makes, models, trims, engines). VIN decoding is planned/future.", "description": "Vehicle hierarchical data lookups (years, makes, models, trims, engines). VIN decoding is planned/future.",
"status": "implemented_vin_decode_planned" "status": "implemented_vin_decode_planned"
},
"stations": {
"path": "backend/src/features/stations/",
"type": "independent_feature",
"self_contained": true,
"external_apis": ["Google Maps API"],
"database_tables": ["stations", "community_stations"],
"cache_strategy": "Station searches: 1 hour",
"status": "implemented"
},
"terms-agreement": {
"path": "backend/src/features/terms-agreement/",
"type": "core_feature",
"self_contained": true,
"database_tables": ["terms_agreements"],
"description": "Legal audit trail for Terms & Conditions acceptance at signup",
"status": "implemented"
},
"user-export": {
"path": "backend/src/features/user-export/",
"type": "independent_feature",
"self_contained": true,
"depends_on": ["vehicles", "fuel-logs", "documents", "maintenance"],
"database_tables": [],
"description": "GDPR-compliant user data export (vehicles, logs, documents as TAR.GZ)",
"status": "implemented"
},
"user-preferences": {
"path": "backend/src/features/user-preferences/",
"type": "core_feature",
"self_contained": true,
"database_tables": ["user_preferences"],
"description": "User preference management (unit system, currency, timezone)",
"status": "implemented"
},
"user-profile": {
"path": "backend/src/features/user-profile/",
"type": "core_feature",
"self_contained": true,
"database_tables": ["user_profiles"],
"description": "User profile management (email, display name, notification email)",
"status": "implemented"
},
"vehicles": {
"path": "backend/src/features/vehicles/",
"type": "core_feature",
"self_contained": true,
"database_tables": ["vehicles"],
"cache_strategy": "User vehicle lists: 5 minutes",
"status": "implemented"
} }
}, },
"feature_dependencies": { "feature_dependencies": {
"explanation": "Logical dependencies within single application service - all deploy together", "explanation": "Logical dependencies within single application service - all deploy together",
"sequence": ["admin", "platform", "vehicles", "fuel-logs", "maintenance", "stations", "documents"] "sequence": ["admin", "auth", "user-profile", "user-preferences", "terms-agreement", "onboarding", "platform", "vehicles", "fuel-logs", "maintenance", "stations", "documents", "notifications", "backup", "user-export"]
}, },
"development_environment": { "development_environment": {
"type": "production_only_docker", "type": "production_only_docker",
@@ -206,7 +275,8 @@
}, },
"external_apis": [ "external_apis": [
"Google Maps API", "Google Maps API",
"Auth0" "Auth0",
"Resend"
] ]
}, },
"network_topology": { "network_topology": {

View File

@@ -0,0 +1,135 @@
# User Preferences Feature
User preference management for unit system, currency, timezone, and dark mode settings.
## Overview
This feature provides API endpoints for retrieving and updating user preferences. Preferences are automatically created with defaults on first access.
## Architecture
- **API Layer**: Routes and controller for HTTP request/response handling
- **Core Integration**: Uses shared `core/user-preferences/` for repository and types
- **Auto-Creation**: Default preferences created on first GET if none exist
## API Endpoints
### GET /api/user/preferences (Protected)
Retrieve user preferences. Creates default preferences if none exist.
**Authentication:** Requires JWT
**Response (200 OK):**
```json
{
"id": "uuid",
"userId": "auth0|123456",
"unitSystem": "imperial",
"currencyCode": "USD",
"timeZone": "UTC",
"darkMode": null,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
```
**Error Responses:**
- 401: Unauthorized (no JWT or invalid JWT)
- 500: Database error
---
### PUT /api/user/preferences (Protected)
Update user preferences. Creates preferences if none exist.
**Authentication:** Requires JWT
**Request:**
```json
{
"unitSystem": "metric",
"currencyCode": "EUR",
"timeZone": "America/New_York",
"darkMode": true
}
```
All fields are optional. Only provided fields are updated.
**Validation:**
- `unitSystem`: Must be `"imperial"` or `"metric"`
- `currencyCode`: Must be a 3-letter ISO currency code (e.g., `"USD"`, `"EUR"`)
- `timeZone`: IANA timezone string (e.g., `"America/New_York"`)
- `darkMode`: Boolean or `null` (null = system default)
**Response (200 OK):**
```json
{
"id": "uuid",
"userId": "auth0|123456",
"unitSystem": "metric",
"currencyCode": "EUR",
"timeZone": "America/New_York",
"darkMode": true,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T12:00:00.000Z"
}
```
**Error Responses:**
- 400: Invalid unitSystem, currencyCode, or darkMode value
- 401: Unauthorized (no JWT or invalid JWT)
- 500: Database error
## Default Values
When preferences are auto-created:
- `unitSystem`: `"imperial"`
- `currencyCode`: `"USD"`
- `timeZone`: `"UTC"`
- `darkMode`: `null` (system default)
## Integration Points
- **Core User Preferences**: Shared repository and types in `core/user-preferences/`
- **Database**: `user_preferences` table
- **Auth**: JWT authentication via Fastify auth plugin
- **Onboarding**: Preferences set during user onboarding flow
## Testing
### Unit Tests
Location: `tests/unit/`
Tests controller logic with mocked repository:
- Get preferences with and without existing record
- Update preferences with valid and invalid inputs
- Validation error handling
Run tests:
```bash
npm test -- features/user-preferences
```
## Dependencies
- UserPreferencesRepository (`core/user-preferences/data/user-preferences.repository.ts`)
- Core logger (`core/logging/logger.ts`)
- Database pool (`core/config/database.ts`)
## Database Table
Table: `user_preferences`
| Column | Type | Description |
|--------|------|-------------|
| id | UUID | Primary key |
| user_id | VARCHAR(255) | Auth0 user ID (unique) |
| unit_system | VARCHAR(20) | `imperial` or `metric` |
| currency_code | VARCHAR(3) | ISO currency code |
| time_zone | VARCHAR(100) | IANA timezone |
| dark_mode | BOOLEAN | Dark mode preference (nullable) |
| created_at | TIMESTAMP | Creation timestamp |
| updated_at | TIMESTAMP | Last update timestamp |
See `docs/DATABASE-SCHEMA.md` for complete schema details.

View File

@@ -240,12 +240,21 @@ Features are **self-contained modules** within the backend service at `backend/s
``` ```
backend/src/features/ backend/src/features/
├── vehicles/ # Vehicle management ├── admin/ # Admin role management
├── fuel-logs/ # Fuel tracking ├── auth/ # User authentication
├── maintenance/ # Service records ├── backup/ # Database backup/restore
├── stations/ # Gas station locations
├── documents/ # Document storage ├── documents/ # Document storage
── platform/ # Vehicle data + VIN decoding ── fuel-logs/ # Fuel tracking
├── maintenance/ # Service records and schedules
├── notifications/ # Email and toast notifications
├── onboarding/ # User onboarding flow
├── platform/ # Vehicle data + VIN decoding
├── stations/ # Gas station locations
├── terms-agreement/ # T&C acceptance audit
├── user-export/ # GDPR data export
├── user-preferences/ # User settings
├── user-profile/ # User profile management
└── vehicles/ # Vehicle management
``` ```
### Feature Structure ### Feature Structure
@@ -262,13 +271,30 @@ feature-name/
└── external/ # External service integrations (optional) └── external/ # External service integrations (optional)
``` ```
### Current Features ### Current Features (15)
#### Vehicles #### Admin
- **Purpose**: Vehicle inventory management - **Purpose**: Admin role management and oversight
- **Tables**: `vehicles` (user-scoped) - **Tables**: `admin_users`, `admin_audit_logs`
- **Integration**: Platform service for make/model/trim data - **Endpoints**: Admin CRUD, catalog management, station oversight
- **Endpoints**: CRUD + dropdown data
#### Auth
- **Purpose**: User authentication and signup
- **Tables**: None (uses Auth0)
- **Integration**: Auth0 for identity management
- **Endpoints**: Signup, verify email, resend verification
#### Backup
- **Purpose**: Database and document backup/restore
- **Tables**: `backup_schedules`, `backup_history`, `backup_settings`
- **Storage**: `/app/data/backups/`
- **Endpoints**: Admin backup CRUD, restore, schedules
#### Documents
- **Purpose**: Document storage and retrieval
- **Tables**: `documents` (user-scoped)
- **Storage**: Filesystem (`/app/data/documents/`)
- **Endpoints**: Upload + download + delete
#### Fuel Logs #### Fuel Logs
- **Purpose**: Fuel purchase and efficiency tracking - **Purpose**: Fuel purchase and efficiency tracking
@@ -278,21 +304,61 @@ feature-name/
#### Maintenance #### Maintenance
- **Purpose**: Service and maintenance record tracking - **Purpose**: Service and maintenance record tracking
- **Tables**: `maintenance_records` (user-scoped) - **Tables**: `maintenance_logs`, `maintenance_schedules` (user-scoped)
- **Integration**: Vehicles feature for vehicle data - **Integration**: Vehicles feature, notifications
- **Endpoints**: CRUD + reminders - **Endpoints**: CRUD + schedules + reminders
#### Notifications
- **Purpose**: Email and toast notifications
- **Tables**: `email_templates`, `notification_logs`
- **Integration**: Resend API for email delivery
- **Endpoints**: User summary, admin template management
#### Onboarding
- **Purpose**: User onboarding after signup
- **Tables**: Uses `user_profiles`, `user_preferences`
- **Integration**: User profile and preferences features
- **Endpoints**: Preferences, complete, status
#### Platform
- **Purpose**: Vehicle data and VIN decoding
- **Tables**: `engines`, `transmissions`, `vehicle_options`
- **Integration**: Backend dropdown cascade queries
- **Endpoints**: Years, makes, models, trims, engines
#### Stations #### Stations
- **Purpose**: Gas station location and pricing - **Purpose**: Gas station location and pricing
- **Tables**: `stations` (user-scoped) - **Tables**: `stations`, `community_stations`
- **Integration**: Google Maps API - **Integration**: Google Maps API
- **Endpoints**: Search + favorites - **Endpoints**: Search + favorites + community reports
#### Documents #### Terms Agreement
- **Purpose**: Document storage and retrieval - **Purpose**: Legal audit trail for T&C acceptance
- **Tables**: `documents` (user-scoped) - **Tables**: `terms_agreements`
- **Storage**: Filesystem (`/app/data/documents/`) - **Integration**: Created during signup flow
- **Endpoints**: Upload + download + delete - **Endpoints**: None (internal use)
#### User Export
- **Purpose**: GDPR-compliant data export
- **Tables**: None (reads from all user data)
- **Storage**: Temporary TAR.GZ archives
- **Endpoints**: Export download
#### User Preferences
- **Purpose**: User preference management
- **Tables**: `user_preferences` (user-scoped)
- **Endpoints**: GET/PUT preferences
#### User Profile
- **Purpose**: User profile management
- **Tables**: `user_profiles` (user-scoped)
- **Endpoints**: GET/PUT profile
#### Vehicles
- **Purpose**: Vehicle inventory management
- **Tables**: `vehicles` (user-scoped)
- **Integration**: Platform service for make/model/trim data
- **Endpoints**: CRUD + dropdown data + images
## Data Flow ## Data Flow

View File

@@ -19,8 +19,3 @@
| `MVP-COLOR-SCHEME.md` | Color scheme reference | UI styling decisions | | `MVP-COLOR-SCHEME.md` | Color scheme reference | UI styling decisions |
| `UX-DEBUGGING.md` | UX debugging techniques | Debugging UI issues | | `UX-DEBUGGING.md` | UX debugging techniques | Debugging UI issues |
## Subdirectories
| Directory | What | When to read |
| --------- | ---- | ------------ |
| `changes/` | Change logs and migration notes | Understanding historical changes |

View File

@@ -1,23 +1,133 @@
# Database Schema Overview # Database Schema Overview
Complete database schema for MotoVaultPro Modified Feature Capsule architecture. Complete database schema for MotoVaultPro 5-container architecture with 15 feature capsules.
## Migration System ## Migration System
### Migration Order (Dependencies) ### Migration Order (Dependencies)
1. **vehicles** - Primary entity, no dependencies 1. **admin** - Admin users and audit logs, no dependencies
2. **fuel-logs** - Depends on vehicles (vehicle_id FK) 2. **user-profile** - User profiles, no dependencies
3. **maintenance** - Depends on vehicles (vehicle_id FK) 3. **user-preferences** - User preferences, depends on user-profile
4. **stations** - Independent feature 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 ### Migration Tracking
- **Table**: `_migrations` - **Table**: `_migrations`
- **Purpose**: Tracks executed migration files to prevent re-execution - **Purpose**: Tracks executed migration files to prevent re-execution
- **Behavior**: Migration system is **idempotent at the file level** - will skip already executed files - **Behavior**: Migration system is **idempotent at the file level** - will skip already executed files
- **SQL Statement Level**: Individual SQL statements within files may fail on re-run if they don't use `IF NOT EXISTS` clauses
- **Safety**: Safe to re-run the migration system; unsafe to manually re-run individual SQL files - **Safety**: Safe to re-run the migration system; unsafe to manually re-run individual SQL files
## Core Tables ## 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 ### vehicles
Primary entity for all vehicle data. Primary entity for all vehicle data.
@@ -26,44 +136,29 @@ Primary entity for all vehicle data.
vehicles ( vehicles (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
user_id VARCHAR(255) NOT NULL, user_id VARCHAR(255) NOT NULL,
vin VARCHAR(17) NOT NULL, vin VARCHAR(17),
make VARCHAR(100), make VARCHAR(100),
model VARCHAR(100), model VARCHAR(100),
year INTEGER, year INTEGER,
trim VARCHAR(100),
engine VARCHAR(100),
transmission VARCHAR(100),
nickname VARCHAR(100), nickname VARCHAR(100),
color VARCHAR(50), color VARCHAR(50),
license_plate VARCHAR(20), license_plate VARCHAR(20),
odometer_reading INTEGER DEFAULT 0, odometer_reading INTEGER DEFAULT 0,
image_bucket VARCHAR(128),
image_key VARCHAR(512),
is_active BOOLEAN DEFAULT true, is_active BOOLEAN DEFAULT true,
deleted_at TIMESTAMP WITH TIME ZONE, -- Soft delete deleted_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE
CONSTRAINT unique_user_vin UNIQUE(user_id, vin)
) )
``` ```
**Indexes**: user_id, vin, is_active, created_at **Indexes**: user_id, vin, is_active, created_at
**Triggers**: auto-update updated_at column **Triggers**: auto-update updated_at
**Constraints**: VIN nullable (license plate can be used instead)
### vin_cache
Caches NHTSA vPIC API responses for 30 days.
```sql
vin_cache (
vin VARCHAR(17) PRIMARY KEY,
make VARCHAR(100),
model VARCHAR(100),
year INTEGER,
engine_type VARCHAR(100),
body_type VARCHAR(100),
raw_data JSONB,
cached_at TIMESTAMP WITH TIME ZONE
)
```
**Indexes**: cached_at (for cleanup)
**TTL**: 30 days (application-managed)
### fuel_logs ### fuel_logs
Tracks fuel purchases and efficiency metrics. Tracks fuel purchases and efficiency metrics.
@@ -74,8 +169,8 @@ fuel_logs (
user_id VARCHAR(255) NOT NULL, user_id VARCHAR(255) NOT NULL,
vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE, vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE,
date_time TIMESTAMP WITH TIME ZONE NOT NULL, date_time TIMESTAMP WITH TIME ZONE NOT NULL,
odometer INTEGER, odometer NUMERIC,
trip_distance DECIMAL(10,2), trip_distance NUMERIC,
fuel_type VARCHAR(50), fuel_type VARCHAR(50),
fuel_grade VARCHAR(50), fuel_grade VARCHAR(50),
fuel_units DECIMAL(10,3) NOT NULL, fuel_units DECIMAL(10,3) NOT NULL,
@@ -88,11 +183,92 @@ fuel_logs (
) )
``` ```
**Foreign Keys**: vehicle_id vehicles.id (ON DELETE CASCADE) **Foreign Keys**: vehicle_id -> vehicles.id (ON DELETE CASCADE)
**Indexes**: user_id, vehicle_id, date_time, created_at **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 ### stations
Gas station locations and details. Gas station locations from Google Maps.
```sql ```sql
stations ( stations (
@@ -104,51 +280,225 @@ stations (
longitude DECIMAL(11,8), longitude DECIMAL(11,8),
phone VARCHAR(20), phone VARCHAR(20),
website VARCHAR(255), website VARCHAR(255),
has_93_octane BOOLEAN,
photo_reference VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE updated_at TIMESTAMP WITH TIME ZONE
) )
``` ```
**External Source**: Google Maps Places API **External Source**: Google Maps Places API
**Storage**: Persisted in PostgreSQL with station_cache table
**Cache Strategy**: Postgres-based cache with TTL management **Cache Strategy**: Postgres-based cache with TTL management
### maintenance ### community_stations
Vehicle maintenance records and scheduling. User-reported station information.
```sql ```sql
maintenance ( community_stations (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
station_id UUID REFERENCES stations(id),
user_id VARCHAR(255) NOT NULL, user_id VARCHAR(255) NOT NULL,
vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE, has_93_octane BOOLEAN,
type VARCHAR(100) NOT NULL, reported_at TIMESTAMP WITH TIME ZONE,
category VARCHAR(50), removed_at TIMESTAMP WITH TIME ZONE,
description TEXT, removal_reason VARCHAR(100)
due_date DATE,
due_mileage INTEGER,
completed_date DATE,
completed_mileage INTEGER,
cost DECIMAL(8,2),
service_location VARCHAR(200),
notes TEXT,
is_completed BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
) )
``` ```
**Foreign Keys**: vehicle_id → vehicles.id (ON DELETE CASCADE) **Foreign Keys**: station_id -> stations.id
**Indexes**: user_id, vehicle_id, due_date, is_completed **Purpose**: Track user reports of 93 octane availability
**Constraints**: Unique(vehicle_id, type), Check(category IN valid values)
## 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 ## Relationships
``` ```
vehicles (1) ──── (many) fuel_logs user_profiles (1) ---- (1) user_preferences
|
└──── (many) maintenance +---- (1) terms_agreements
stations (independent - no FK relationships) 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 ## Data Constraints
@@ -159,16 +509,16 @@ stations (independent - no FK relationships)
- No cross-user data access possible - No cross-user data access possible
### Referential Integrity ### Referential Integrity
- fuel_logs.vehicle_id → vehicles.id (ON DELETE CASCADE) - fuel_logs, maintenance_logs, maintenance_schedules, documents -> vehicles.id (ON DELETE CASCADE)
- maintenance.vehicle_id → vehicles.id (ON DELETE CASCADE) - community_stations -> stations.id
- Cascading deletes ensure related logs/maintenance are removed when vehicle is deleted - vehicle_options -> engines.id, transmissions.id (ON DELETE SET NULL)
- Soft deletes on vehicles (deleted_at) may result in orphaned hard-deleted related records - backup_history -> backup_schedules.id (ON DELETE SET NULL)
### VIN Validation ### VIN Validation
- Exactly 17 characters - 17 characters when provided (now optional)
- Cannot contain letters I, O, or Q - Cannot contain letters I, O, or Q
- Application-level checksum validation - Application-level checksum validation
- Unique per user (same VIN can exist for different users) - Either VIN or license_plate required
## Caching Strategy ## Caching Strategy
@@ -177,13 +527,8 @@ stations (independent - no FK relationships)
- **VIN decodes**: 7 days (key: `vin:decode:{vin}`) - **VIN decodes**: 7 days (key: `vin:decode:{vin}`)
- **User vehicle lists**: 5 minutes (key: `vehicles:user:{userId}`) - **User vehicle lists**: 5 minutes (key: `vehicles:user:{userId}`)
- **Fuel logs per vehicle**: 5 minutes (key: `fuel-logs:vehicle:{vehicleId}:{unitSystem}`) - **Fuel logs per vehicle**: 5 minutes (key: `fuel-logs:vehicle:{vehicleId}:{unitSystem}`)
- **Vehicle statistics**: Real-time (no caching, fresh queries)
- **Maintenance data**: Unit system-aware caching where applicable - **Maintenance data**: Unit system-aware caching where applicable
### Database-Level Caching
- **vin_cache table**: Persistent cache for VIN decodes
- **Cleanup**: Application-managed based on TTL strategy
## Migration Commands ## Migration Commands
### Run All Migrations ### Run All Migrations
@@ -194,7 +539,6 @@ npm run migrate:all
# Via Docker # Via Docker
make migrate make migrate
``` ```
Single-feature migration is not implemented yet.
### Migration Files ### Migration Files
- **Location**: `backend/src/features/[feature]/migrations/` - **Location**: `backend/src/features/[feature]/migrations/`
@@ -205,29 +549,13 @@ Single-feature migration is not implemented yet.
### Development (Docker) ### Development (Docker)
- **Host**: mvp-postgres (container name) - **Host**: mvp-postgres (container name)
- **Port**: 5432 (internal), 5432 (external) - **Port**: 5432
- **Database**: motovaultpro - **Database**: motovaultpro
- **User**: postgres - **User**: postgres
- **Password**: Loaded from secrets file `/run/secrets/postgres-password` - **Password**: Loaded from secrets file `/run/secrets/postgres-password`
**Password Management**: All database passwords are managed via Docker secrets, mounted from host files:
- Application DB: `./secrets/app/postgres-password.txt`
### Connection Pool ### Connection Pool
- **Implementation**: pg (node-postgres) - **Implementation**: pg (node-postgres)
- **Pool Size**: Default (10 connections) - **Pool Size**: Default (10 connections)
- **Idle Timeout**: 30 seconds - **Idle Timeout**: 30 seconds
- **Location**: `backend/src/core/config/database.ts` - **Location**: `backend/src/core/config/database.ts`
## Backup Strategy
### Development
- **Docker Volume**: `postgres_data`
- **Persistence**: Survives container restarts
- **Reset**: `make clean` removes all data
### Production Considerations
- Regular pg_dump backups
- Point-in-time recovery
- Read replicas for analytics
- Connection pooling (PgBouncer)

View File

@@ -10,18 +10,25 @@ Project documentation hub for the 5-container single-tenant architecture with in
- Database schema: `docs/DATABASE-SCHEMA.md` - Database schema: `docs/DATABASE-SCHEMA.md`
- Testing (containers only): `docs/TESTING.md` - Testing (containers only): `docs/TESTING.md`
- Database Migration: `docs/DATABASE-MIGRATION.md` - Database Migration: `docs/DATABASE-MIGRATION.md`
- Admin feature: `docs/ADMIN.md` - Role management, APIs, catalog CRUD, station oversight
- Development Environment: `docker-compose.yml` - Development Environment: `docker-compose.yml`
- Application features (start at each README): - Application features (start at each README):
- `backend/src/features/admin/README.md` - Admin role management and oversight - `backend/src/features/admin/README.md` - Admin role management and oversight
- `backend/src/features/platform/README.md` - Vehicle data and VIN decoding - `backend/src/features/auth/README.md` - User signup and email verification
- `backend/src/features/vehicles/README.md` - User vehicle management - `backend/src/features/backup/README.md` - Database backup and restore
- `backend/src/features/documents/README.md` - Document storage and management
- `backend/src/features/fuel-logs/README.md` - Fuel consumption tracking - `backend/src/features/fuel-logs/README.md` - Fuel consumption tracking
- `backend/src/features/maintenance/README.md` - Maintenance records - `backend/src/features/maintenance/README.md` - Maintenance records
- `backend/src/features/notifications/README.md` - Email and toast notifications
- `backend/src/features/onboarding/README.md` - User onboarding flow
- `backend/src/features/platform/README.md` - Vehicle data and VIN decoding
- `backend/src/features/stations/README.md` - Gas station search and favorites (Google Maps integration) - `backend/src/features/stations/README.md` - Gas station search and favorites (Google Maps integration)
- `backend/src/features/documents/README.md` - Document storage and management - `backend/src/features/terms-agreement/README.md` - Terms & Conditions acceptance audit
- `backend/src/features/user-export/README.md` - User data export (GDPR)
- `backend/src/features/user-preferences/README.md` - User preference settings
- `backend/src/features/user-profile/README.md` - User profile management
- `backend/src/features/vehicles/README.md` - User vehicle management
## Notes ## Notes
- Canonical URLs: Frontend `https://motovaultpro.com`, Backend health `https://motovaultpro.com/api/health`. - Canonical URLs: Frontend `https://motovaultpro.com`, Backend health `https://motovaultpro.com/api/health`.
- All 7 features have comprehensive test suites (unit + integration tests). - All 15 features have comprehensive test suites (unit + integration tests).

View File

@@ -176,13 +176,21 @@ make clean && make start
- **Seeding**: Use feature-level fixtures when needed - **Seeding**: Use feature-level fixtures when needed
### Coverage and Availability ### Coverage and Availability
All features have comprehensive test suites with unit and integration tests: All 15 features have test suites with unit and/or integration tests:
- `admin` - Unit + integration tests - `admin` - Unit + integration tests
- `auth` - Unit + integration tests
- `backup` - Unit + integration tests
- `documents` - Unit + integration tests - `documents` - Unit + integration tests
- `fuel-logs` - Unit + integration tests - `fuel-logs` - Unit + integration tests
- `maintenance` - Unit + integration tests - `maintenance` - Unit + integration tests
- `notifications` - Unit + integration tests
- `onboarding` - Unit tests
- `platform` - Unit + integration tests - `platform` - Unit + integration tests
- `stations` - Unit + integration tests (including community stations) - `stations` - Unit + integration tests (including community stations)
- `terms-agreement` - Unit tests
- `user-export` - Unit tests
- `user-preferences` - Unit tests
- `user-profile` - Unit + integration tests
- `vehicles` - Unit + integration tests - `vehicles` - Unit + integration tests
### Mock Strategy ### Mock Strategy

View File

@@ -1,167 +1,434 @@
# Vehicles API Platform Rebuild, App Integration, and Operations # Vehicles API Reference
This document explains the endtoend Vehicles API architecture after the platform service rebuild, how the MotoVaultPro app consumes it, how migrations/seeding work, and how to operate the stack in productiononly development. Complete API documentation for the vehicles feature in MotoVaultPro.
## Overview ## Overview
- Architecture: MotoVaultPro backend (Fastify + TypeScript) includes an integrated platform module that shares Postgres and Redis with the rest of the stack.
- Goal: Predictable year→make→model→trim→engine cascades, productiononly workflow, AIfriendly code layout and docs.
## Platform Vehicles Module The Vehicles API provides endpoints for managing user vehicles, including CRUD operations, image management, and hierarchical dropdown data for vehicle selection forms.
### Database Schema (Postgres schema: `vehicles`) **Authentication**: All endpoints require JWT authentication via `Authorization: Bearer <token>` header.
- `make(id, name)`
- `model(id, make_id → make.id, name)`
- `model_year(id, model_id → model.id, year)`
- `trim(id, model_year_id → model_year.id, name)`
- `engine(id, name, code, displacement_l, cylinders, fuel_type, aspiration)`
- `trim_engine(trim_id → trim.id, engine_id → engine.id)`
- Optional (present, not exposed yet): `transmission`, `trim_transmission`, `performance`
Idempotent constraints/indexes added where applicable (e.g., unique lower(name), unique(model_id, year), guarded `CREATE INDEX IF NOT EXISTS`, guarded trigger). ## Base URL
### Dropdown API (Bearer auth required) ```
Prefix: `/api/vehicles/dropdown` https://motovaultpro.com/api
- `GET /years``[number]` (latest to oldest model years) ```
- `GET /makes?year={year}``{ id, name }[]` (makes available in that year)
- `GET /models?year={year}&make_id={make}``{ id, name }[]` (models filtered by year + make)
- `GET /trims?year={year}&make_id={make}&model_id={model}``{ id, name }[]` (trims for the selection)
- `GET /engines?year={year}&make_id={make}&model_id={model}&trim_id={trim}``{ id, name }[]` (engines for the trim)
- `GET /transmissions?year={year}&make_id={make}&model_id={model}``{ id, name }[]` (`Automatic`, `Manual`)
Selection order is enforced: each endpoint requires the IDs returned by the previous stage, so users never see invalid combinations (e.g., 2023 Chevrolet excludes the S-10, 1999 GMC trims exclude AT4X, etc.). ## Vehicle CRUD Operations
### Platform module (internal bridge) ### List User Vehicles
Prefix: `/api/platform`
- Same hierarchical endpoints as above, but responses are wrapped (`{ makes: [...] }`, `{ models: [...] }`) for service-to-service integrations.
- VIN decode endpoint: `GET /api/platform/vehicle?vin={vin}`
### Authentication Returns all vehicles for the authenticated user.
- Auth0 JWT via `Authorization: Bearer ${JWT_TOKEN}` (required for both `/api/vehicles/*` and `/api/platform/*`)
- Configured in backend: `src/core/plugins/auth.plugin.ts` with JWKS validation
### Caching (Redis) ```
- Keys: `platform:years`, `platform:vehicle-data:makes:{year}`, `platform:vehicle-data:models:{year}:{make}`, `platform:vehicle-data:trims:{year}:{model}`, `platform:vehicle-data:engines:{year}:{model}:{trim}` GET /api/vehicles
- Dropdown TTL: 6 hours (`21600s`) ```
- VIN decode TTL: 7 days (`604800s`) via `platform:vin-decode:{vin}`
- Implementation: `backend/src/features/platform/domain/platform-cache.service.ts`
### Seeds & Specific Examples **Response** (200):
Platform seed migrations (TypeScript backend): ```json
- Schema definition (`backend/src/features/platform/migrations/001_create_platform_schema.sql`) [
- Constraints and indexes (`backend/src/features/platform/migrations/002_create_indexes.sql`) {
- Sample vehicle data (`backend/src/features/platform/migrations/003_seed_vehicles.sql`) "id": "uuid",
Seeds are auto-migrated on backend container start via `backend/src/_system/migrations/run-all.ts`. "userId": "auth0|123456",
"vin": "1HGBH41JXMN109186",
"make": "Honda",
"model": "Civic",
"year": 2021,
"engine": "2.0L 4-Cylinder",
"transmission": "CVT Automatic",
"trimLevel": "EX",
"driveType": "FWD",
"fuelType": "Gasoline",
"nickname": "My Honda",
"color": "Blue",
"licensePlate": "ABC123",
"odometerReading": 15000,
"isActive": true,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"imageUrl": "/api/vehicles/{id}/image"
}
]
```
Clear platform cache: ### Get Single Vehicle
- `docker compose exec -T mvp-redis sh -lc "redis-cli FLUSHALL"`
- Or restart containers: `make rebuild`
## MotoVaultPro Backend (Application Service) ```
GET /api/vehicles/:id
```
### Proxy Dropdown Endpoints **Parameters**:
Vehicles service simply re-emits the data returned by `Platform VehicleDataService`, normalizing it to `{ id, name }[]` arrays for frontend consumption. Redis caching and Postgres lookups all remain centralized in the platform module. - `id` (path): Vehicle UUID
### Platform Integration **Response** (200): Single vehicle object (same structure as list item)
- `VehiclesService` lazily instantiates `VehicleDataService` via `getVehicleDataService()`, guaranteeing a shared cache.
- Each dropdown call passes the shared Postgres pool (`getPool()`) and propagates selection context (year/make/model/trim).
- Transmission dropdown is intentionally static (`Automatic`, `Manual`) until richer metadata is available.
### Authentication (App) **Errors**:
- Auth0 JWT enforced via Fastify + JWKS. No mock users. - `404`: Vehicle not found or not owned by user
### Migrations (ProductionQuality) ### Create Vehicle
- Migrations packaged in image under `/app/migrations/features/[feature]/migrations`.
- Runner (`backend/src/_system/migrations/run-all.ts`):
- Reads base dir from `MIGRATIONS_DIR` (env in Dockerfile)
- Tracks executed files in `_migrations` table and skips already executed files
- **Idempotent at file level**: Safe to re-run migration system multiple times
- Wait/retry for DB readiness to avoid flapping on cold starts
- Automigrate on backend container start: `node dist/_system/migrations/run-all.js && npm start`
- Manual: `make migrate` (runs runner inside the container)
## Frontend Changes ```
- Vehicles form cascades: year → make → model → trim → engine. POST /api/vehicles
- Engines load only after a trim is selected (requires `trim_id`). ```
- Validation updated: user must provide either a 17char VIN or a nonempty license plate.
- VIN Decode button still requires a valid 17char VIN.
- APIs used:
- `/api/vehicles/dropdown/years`
- `/api/vehicles/dropdown/makes|models|trims|engines`
- `/api/vehicles/dropdown/transmissions`
## Add Vehicle Form Change/Add/Modify/Delete Fields (Fast Track) **Request Body**:
```json
{
"vin": "1HGBH41JXMN109186",
"year": 2021,
"make": "Honda",
"model": "Civic",
"engine": "2.0L 4-Cylinder",
"transmission": "CVT Automatic",
"trimLevel": "EX",
"driveType": "FWD",
"fuelType": "Gasoline",
"nickname": "My Honda",
"color": "Blue",
"licensePlate": "ABC123",
"odometerReading": 15000
}
```
Where to edit **Required**: Either `vin` OR `licensePlate` must be provided (not both required).
- UI + validation: `frontend/src/features/vehicles/components/VehicleForm.tsx`
- Frontend types: `frontend/src/features/vehicles/types/vehicles.types.ts`
- Backend controller/service/repo: `backend/src/features/vehicles/api/vehicles.controller.ts`, `domain/vehicles.service.ts`, `data/vehicles.repository.ts`, types in `domain/vehicles.types.ts`
- App DB migrations: `backend/src/features/vehicles/migrations/*.sql` (automigrated on backend start)
Add a new field (example: bodyStyle) **Response** (201): Created vehicle object
1) DB: `ALTER TABLE vehicles ADD COLUMN IF NOT EXISTS body_style VARCHAR(100);` in a new migration file.
2) Backend: add `bodyStyle?: string;` to types; include in repository insert/update mapping as `body_style`.
3) Frontend: add `bodyStyle` to Zod schema and a new input bound via `register('bodyStyle')`.
4) Rebuild frontend/backend and verify in Network + logs.
Modify an existing field ### Update Vehicle
- Update labels/placeholders in VehicleForm.
- Update Zod schema for new validation rules; mirror on the server if desired.
- Adjust service logic only if business behavior changes.
Delete a field (safe path) ```
- Remove from VehicleForm and frontend types. PUT /api/vehicles/:id
- Remove from backend types/repository mapping. ```
- Optional migration to drop the column later.
Dropdown ordering **Parameters**:
- Implemented in VehicleForm; current order is Year → Make → Model → Trim → Engine → Transmission (static). - `id` (path): Vehicle UUID
- Engine select is enabled only after a Trim is selected.
VIN/License rule **Request Body**: Same fields as create (all optional, only provided fields are updated)
- Frontend Zod: either 17char VIN or nonempty license plate; if no plate, VIN must be 17.
- Backend controller enforces the same rule; service decodes/validates only when VIN is present.
- Repository normalizes empty VIN to NULL to avoid unique collisions.
## Operations **Response** (200): Updated vehicle object
### Rebuild a single service ### Delete Vehicle
- Frontend: `docker compose up -d --build frontend`
- Backend (includes platform module): `docker compose up -d --build backend`
### Logs & Health Soft-deletes a vehicle (sets `deleted_at` timestamp).
- Backend: `https://motovaultpro.com/api/health` shows status/feature list, including platform readiness
- Logs: `make logs-backend`, `make logs-frontend`
### Common Reset Sequences ```
- Platform seed reapply (nondestructive): apply `005_seed_specific_vehicles.sql` and flush Redis cache. DELETE /api/vehicles/:id
- Platform reset (WARNING - DESTRUCTIVE to shared resources): ```
- `docker compose rm -sf mvp-postgres mvp-redis`
- `docker volume rm motovaultpro_postgres_data motovaultpro_redis_data`
- `docker compose up -d mvp-postgres mvp-redis mvp-backend`
- Note: This will destroy ALL application data, not just platform data, as database and cache are shared
## Security Summary **Parameters**:
- Platform Module: Auth0 JWT via `Authorization: Bearer ${JWT_TOKEN}` required on all `/api/platform/*` endpoints. - `id` (path): Vehicle UUID
- Vehicles Feature: Auth0 JWT required on all protected `/api/vehicles/*` routes.
- Health Check: `/api/health` is unauthenticated (Traefik readiness probe).
## CI Summary **Response** (204): No content
- Workflow `.github/workflows/ci.yml` builds backend/frontend/platform API.
- Runs backend lint/tests in a builder image on a stable network.
## Troubleshooting ## Vehicle Images
- Frontend shows generic “Server error” right after login:
- Check backend `/api/vehicles` 500s (migrations not run or DB unavailable).
- Run `make migrate` or ensure backend container automigrate is succeeding; check `docker compose logs backend`.
- Dropdowns not updating after seed:
- Run specific seed SQL (see above) and `redis-cli FLUSHALL` on shared Redis (mvp-redis).
- Backend flapping on start after rebuild:
- Ensure Postgres is up; the runner now waits/retries, but confirm logs.
## Notable Files ### Upload Image
- Platform schema & seeds: maintained by database admins (legacy FastAPI scripts available on request)
- Platform API integration: `backend/src/features/platform/api/*` Upload or replace the vehicle's image.
- Backend dropdown proxy: `backend/src/features/vehicles/api/*`
- Backend platform module: `backend/src/features/platform/*` ```
- Backend migrations runner: `backend/src/_system/migrations/run-all.ts` POST /api/vehicles/:id/image
- Frontend vehicles UI: `frontend/src/features/vehicles/*` Content-Type: multipart/form-data
```
**Parameters**:
- `id` (path): Vehicle UUID
**Form Data**:
- `image`: Image file (JPEG, PNG, WebP; max 5MB)
**Response** (200):
```json
{
"success": true,
"imageUrl": "/api/vehicles/{id}/image"
}
```
### Download Image
```
GET /api/vehicles/:id/image
```
**Parameters**:
- `id` (path): Vehicle UUID
**Response**: Image binary with appropriate `Content-Type` header
**Errors**:
- `404`: No image exists for vehicle
### Delete Image
```
DELETE /api/vehicles/:id/image
```
**Parameters**:
- `id` (path): Vehicle UUID
**Response** (204): No content
## Dropdown Cascade API
Hierarchical endpoints for vehicle selection dropdowns. Each level filters based on previous selections.
### Get Years
Returns available model years in descending order.
```
GET /api/vehicles/dropdown/years
```
**Response** (200):
```json
{
"years": [2025, 2024, 2023, 2022, ...]
}
```
### Get Makes
Returns makes available for a specific year.
```
GET /api/vehicles/dropdown/makes?year={year}
```
**Query Parameters**:
- `year` (required): Model year (e.g., 2024)
**Response** (200):
```json
{
"makes": ["Acura", "Audi", "BMW", "Chevrolet", "Ford", ...]
}
```
### Get Models
Returns models available for a specific year and make.
```
GET /api/vehicles/dropdown/models?year={year}&make={make}
```
**Query Parameters**:
- `year` (required): Model year
- `make` (required): Make name (e.g., "Ford")
**Response** (200):
```json
{
"models": ["Bronco", "Edge", "Explorer", "F-150", "Mustang", ...]
}
```
### Get Trims
Returns trims available for a specific year, make, and model.
```
GET /api/vehicles/dropdown/trims?year={year}&make={make}&model={model}
```
**Query Parameters**:
- `year` (required): Model year
- `make` (required): Make name
- `model` (required): Model name (e.g., "F-150")
**Response** (200):
```json
{
"trims": ["XL", "XLT", "Lariat", "King Ranch", "Platinum", ...]
}
```
### Get Engines
Returns engines available for a specific year, make, model, and trim.
```
GET /api/vehicles/dropdown/engines?year={year}&make={make}&model={model}&trim={trim}
```
**Query Parameters**:
- `year` (required): Model year
- `make` (required): Make name
- `model` (required): Model name
- `trim` (required): Trim level (e.g., "XLT")
**Response** (200):
```json
{
"engines": [
"3.3L V6 Ti-VCT",
"2.7L V6 EcoBoost",
"5.0L V8 Ti-VCT",
"3.5L V6 EcoBoost"
]
}
```
### Get Transmissions
Returns transmissions available for a specific year, make, model, and trim.
```
GET /api/vehicles/dropdown/transmissions?year={year}&make={make}&model={model}&trim={trim}
```
**Query Parameters**:
- `year` (required): Model year
- `make` (required): Make name
- `model` (required): Model name
- `trim` (required): Trim level
**Response** (200):
```json
{
"transmissions": [
"10-Speed Automatic",
"6-Speed Automatic"
]
}
```
### Get Pair-Safe Options
Returns valid engine/transmission combinations, ensuring only compatible pairs are shown.
```
GET /api/vehicles/dropdown/options?year={year}&make={make}&model={model}&trim={trim}[&engine={engine}][&transmission={transmission}]
```
**Query Parameters**:
- `year` (required): Model year
- `make` (required): Make name
- `model` (required): Model name
- `trim` (required): Trim level
- `engine` (optional): If provided, returns only transmissions compatible with this engine
- `transmission` (optional): If provided, returns only engines compatible with this transmission
**Response** (200):
```json
{
"engines": ["3.3L V6 Ti-VCT", "2.7L V6 EcoBoost"],
"transmissions": ["10-Speed Automatic"]
}
```
## Platform API
Direct platform endpoints for vehicle data lookups (alternative to dropdown cascade).
```
GET /api/platform/years
GET /api/platform/makes?year={year}
GET /api/platform/models?year={year}&make={make}
GET /api/platform/trims?year={year}&make={make}&model={model}
GET /api/platform/engines?year={year}&make={make}&model={model}&trim={trim}
GET /api/platform/transmissions?year={year}&make={make}&model={model}&trim={trim}
```
Same functionality as dropdown cascade API but with `/api/platform/` prefix.
## Error Responses
All endpoints return standard error responses:
```json
{
"error": "Error message",
"statusCode": 400
}
```
**Common Status Codes**:
- `400`: Bad request (validation error)
- `401`: Unauthorized (missing or invalid JWT)
- `404`: Resource not found
- `500`: Internal server error
## Data Model
### Vehicle Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | UUID | Auto | Primary key |
| userId | string | Auto | Auth0 user ID (from JWT) |
| vin | string | No* | Vehicle Identification Number (17 chars) |
| make | string | No | Manufacturer (e.g., "Ford") |
| model | string | No | Model name (e.g., "F-150") |
| year | integer | No | Model year |
| engine | string | No | Engine specification |
| transmission | string | No | Transmission type |
| trimLevel | string | No | Trim level (e.g., "XLT") |
| driveType | string | No | Drive type (e.g., "4WD", "FWD") |
| fuelType | string | No | Fuel type (e.g., "Gasoline", "Diesel") |
| nickname | string | No | User-defined name |
| color | string | No | Vehicle color |
| licensePlate | string | No* | License plate number |
| odometerReading | integer | No | Current odometer (default: 0) |
| isActive | boolean | Auto | Active status (default: true) |
| createdAt | datetime | Auto | Creation timestamp |
| updatedAt | datetime | Auto | Last update timestamp |
| imageUrl | string | Auto | URL to vehicle image (if uploaded) |
*Either `vin` OR `licensePlate` should be provided for vehicle identification.
### VIN Validation
When a VIN is provided:
- Must be exactly 17 characters
- Cannot contain letters I, O, or Q
- Check digit validation is performed
## Database Schema
### Platform Tables
Vehicle lookup data is stored in three denormalized tables optimized for dropdown cascade queries:
- **engines**: Engine specifications (name, displacement, horsepower, fuel_type, etc.)
- **transmissions**: Transmission types (type, speeds, drive_type)
- **vehicle_options**: Denormalized year/make/model/trim combinations with engine/transmission references
See `docs/DATABASE-SCHEMA.md` for complete table definitions.
### Migration Location
Platform migrations: `backend/src/features/platform/migrations/001_create_vehicle_lookup_schema.sql`
## Caching
- **Dropdown data**: Cached in Redis for 6 hours (key: `dropdown:{dataType}:{params}`)
- **User vehicle lists**: Cached for 5 minutes (key: `vehicles:user:{userId}`)
- Cache is invalidated on vehicle create/update/delete operations
## Frontend Integration
### Form Cascade Order
The vehicle form uses cascading dropdowns in this order:
1. Year (loads makes)
2. Make (loads models)
3. Model (loads trims)
4. Trim (loads engines and transmissions)
5. Engine and Transmission (pair-safe filtering via `/dropdown/options`)
### Files
- Form component: `frontend/src/features/vehicles/components/VehicleForm.tsx`
- Types: `frontend/src/features/vehicles/types/vehicles.types.ts`
- API hooks: `frontend/src/features/vehicles/hooks/`
## Related Documentation
- Feature README: `backend/src/features/vehicles/README.md`
- Platform README: `backend/src/features/platform/README.md`
- Database Schema: `docs/DATABASE-SCHEMA.md`
- Architecture: `docs/ARCHITECTURE-OVERVIEW.md`