diff --git a/.ai/context.json b/.ai/context.json index 1b5edf4..c343144 100644 --- a/.ai/context.json +++ b/.ai/context.json @@ -115,12 +115,29 @@ "description": "Admin role management, platform catalog CRUD, station oversight", "status": "implemented" }, - "vehicles": { - "path": "backend/src/features/vehicles/", + "auth": { + "path": "backend/src/features/auth/", "type": "core_feature", "self_contained": true, - "database_tables": ["vehicles"], - "cache_strategy": "User vehicle lists: 5 minutes", + "database_tables": [], + "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" }, "fuel-logs": { @@ -141,21 +158,23 @@ "cache_strategy": "Upcoming maintenance: 1 hour", "status": "implemented" }, - "stations": { - "path": "backend/src/features/stations/", - "type": "independent_feature", + "notifications": { + "path": "backend/src/features/notifications/", + "type": "dependent_feature", "self_contained": true, - "external_apis": ["Google Maps API"], - "database_tables": ["stations", "community_stations"], - "cache_strategy": "Station searches: 1 hour", + "depends_on": ["maintenance", "documents"], + "database_tables": ["email_templates", "notification_logs", "sent_notification_tracker", "user_notifications"], + "external_apis": ["Resend"], + "description": "Email and toast notifications for maintenance due/overdue and expiring documents", "status": "implemented" }, - "documents": { - "path": "backend/src/features/documents/", - "type": "independent_feature", + "onboarding": { + "path": "backend/src/features/onboarding/", + "type": "dependent_feature", "self_contained": true, - "database_tables": ["documents"], - "storage": "/app/data/documents/", + "depends_on": ["user-profile", "user-preferences"], + "database_tables": [], + "description": "User onboarding flow after email verification (preferences, first vehicle)", "status": "implemented" }, "platform": { @@ -166,11 +185,61 @@ "cache_strategy": "Vehicle hierarchical data: 6 hours", "description": "Vehicle hierarchical data lookups (years, makes, models, trims, engines). VIN decoding is planned/future.", "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": { "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": { "type": "production_only_docker", @@ -206,7 +275,8 @@ }, "external_apis": [ "Google Maps API", - "Auth0" + "Auth0", + "Resend" ] }, "network_topology": { diff --git a/backend/src/features/user-preferences/README.md b/backend/src/features/user-preferences/README.md new file mode 100644 index 0000000..fe60eea --- /dev/null +++ b/backend/src/features/user-preferences/README.md @@ -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. diff --git a/docs/ARCHITECTURE-OVERVIEW.md b/docs/ARCHITECTURE-OVERVIEW.md index baf01a4..55af73c 100644 --- a/docs/ARCHITECTURE-OVERVIEW.md +++ b/docs/ARCHITECTURE-OVERVIEW.md @@ -240,12 +240,21 @@ Features are **self-contained modules** within the backend service at `backend/s ``` backend/src/features/ -├── vehicles/ # Vehicle management -├── fuel-logs/ # Fuel tracking -├── maintenance/ # Service records -├── stations/ # Gas station locations +├── admin/ # Admin role management +├── auth/ # User authentication +├── backup/ # Database backup/restore ├── 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 @@ -262,13 +271,30 @@ feature-name/ └── external/ # External service integrations (optional) ``` -### Current Features +### Current Features (15) -#### Vehicles -- **Purpose**: Vehicle inventory management -- **Tables**: `vehicles` (user-scoped) -- **Integration**: Platform service for make/model/trim data -- **Endpoints**: CRUD + dropdown data +#### Admin +- **Purpose**: Admin role management and oversight +- **Tables**: `admin_users`, `admin_audit_logs` +- **Endpoints**: Admin CRUD, catalog management, station oversight + +#### 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 - **Purpose**: Fuel purchase and efficiency tracking @@ -278,21 +304,61 @@ feature-name/ #### Maintenance - **Purpose**: Service and maintenance record tracking -- **Tables**: `maintenance_records` (user-scoped) -- **Integration**: Vehicles feature for vehicle data -- **Endpoints**: CRUD + reminders +- **Tables**: `maintenance_logs`, `maintenance_schedules` (user-scoped) +- **Integration**: Vehicles feature, notifications +- **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 - **Purpose**: Gas station location and pricing -- **Tables**: `stations` (user-scoped) +- **Tables**: `stations`, `community_stations` - **Integration**: Google Maps API -- **Endpoints**: Search + favorites +- **Endpoints**: Search + favorites + community reports -#### Documents -- **Purpose**: Document storage and retrieval -- **Tables**: `documents` (user-scoped) -- **Storage**: Filesystem (`/app/data/documents/`) -- **Endpoints**: Upload + download + delete +#### Terms Agreement +- **Purpose**: Legal audit trail for T&C acceptance +- **Tables**: `terms_agreements` +- **Integration**: Created during signup flow +- **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 diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 4af9023..0a33a36 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -19,8 +19,3 @@ | `MVP-COLOR-SCHEME.md` | Color scheme reference | UI styling decisions | | `UX-DEBUGGING.md` | UX debugging techniques | Debugging UI issues | -## Subdirectories - -| Directory | What | When to read | -| --------- | ---- | ------------ | -| `changes/` | Change logs and migration notes | Understanding historical changes | diff --git a/docs/DATABASE-SCHEMA.md b/docs/DATABASE-SCHEMA.md index 4c65538..ced6224 100644 --- a/docs/DATABASE-SCHEMA.md +++ b/docs/DATABASE-SCHEMA.md @@ -1,23 +1,133 @@ # 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 Order (Dependencies) -1. **vehicles** - Primary entity, no dependencies -2. **fuel-logs** - Depends on vehicles (vehicle_id FK) -3. **maintenance** - Depends on vehicles (vehicle_id FK) -4. **stations** - Independent feature +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 -- **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 -## 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 Primary entity for all vehicle data. @@ -26,44 +136,29 @@ Primary entity for all vehicle data. vehicles ( id UUID PRIMARY KEY, user_id VARCHAR(255) NOT NULL, - vin VARCHAR(17) NOT NULL, + vin VARCHAR(17), make VARCHAR(100), - model 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, -- Soft delete + deleted_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE, - updated_at TIMESTAMP WITH TIME ZONE, - - CONSTRAINT unique_user_vin UNIQUE(user_id, vin) + updated_at TIMESTAMP WITH TIME ZONE ) ``` **Indexes**: user_id, vin, is_active, created_at -**Triggers**: auto-update updated_at column - -### 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) +**Triggers**: auto-update updated_at +**Constraints**: VIN nullable (license plate can be used instead) ### fuel_logs Tracks fuel purchases and efficiency metrics. @@ -74,8 +169,8 @@ fuel_logs ( 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 INTEGER, - trip_distance DECIMAL(10,2), + odometer NUMERIC, + trip_distance NUMERIC, fuel_type VARCHAR(50), fuel_grade VARCHAR(50), 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 +### 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 and details. +Gas station locations from Google Maps. ```sql stations ( @@ -104,51 +280,225 @@ stations ( 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 -**Storage**: Persisted in PostgreSQL with station_cache table **Cache Strategy**: Postgres-based cache with TTL management -### maintenance -Vehicle maintenance records and scheduling. +### community_stations +User-reported station information. ```sql -maintenance ( +community_stations ( id UUID PRIMARY KEY, + station_id UUID REFERENCES stations(id), user_id VARCHAR(255) NOT NULL, - vehicle_id UUID NOT NULL REFERENCES vehicles(id) ON DELETE CASCADE, - type VARCHAR(100) NOT NULL, - category VARCHAR(50), - description TEXT, - 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 + has_93_octane BOOLEAN, + reported_at TIMESTAMP WITH TIME ZONE, + removed_at TIMESTAMP WITH TIME ZONE, + removal_reason VARCHAR(100) ) ``` -**Foreign Keys**: vehicle_id → vehicles.id (ON DELETE CASCADE) -**Indexes**: user_id, vehicle_id, due_date, is_completed -**Constraints**: Unique(vehicle_id, type), Check(category IN valid values) +**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 ``` -vehicles (1) ──── (many) fuel_logs - │ - └──── (many) maintenance +user_profiles (1) ---- (1) user_preferences + | + +---- (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 @@ -159,16 +509,16 @@ stations (independent - no FK relationships) - No cross-user data access possible ### Referential Integrity -- fuel_logs.vehicle_id → vehicles.id (ON DELETE CASCADE) -- maintenance.vehicle_id → vehicles.id (ON DELETE CASCADE) -- Cascading deletes ensure related logs/maintenance are removed when vehicle is deleted -- Soft deletes on vehicles (deleted_at) may result in orphaned hard-deleted related records +- 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 -- Exactly 17 characters +- 17 characters when provided (now optional) - Cannot contain letters I, O, or Q - Application-level checksum validation -- Unique per user (same VIN can exist for different users) +- Either VIN or license_plate required ## Caching Strategy @@ -177,13 +527,8 @@ stations (independent - no FK relationships) - **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}`) -- **Vehicle statistics**: Real-time (no caching, fresh queries) - **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 ### Run All Migrations @@ -194,7 +539,6 @@ npm run migrate:all # Via Docker make migrate ``` -Single-feature migration is not implemented yet. ### Migration Files - **Location**: `backend/src/features/[feature]/migrations/` @@ -205,29 +549,13 @@ Single-feature migration is not implemented yet. ### Development (Docker) - **Host**: mvp-postgres (container name) -- **Port**: 5432 (internal), 5432 (external) +- **Port**: 5432 - **Database**: motovaultpro - **User**: postgres - **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 - **Implementation**: pg (node-postgres) - **Pool Size**: Default (10 connections) - **Idle Timeout**: 30 seconds - **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) diff --git a/docs/README.md b/docs/README.md index 4f3bdeb..77d4298 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,18 +10,25 @@ Project documentation hub for the 5-container single-tenant architecture with in - Database schema: `docs/DATABASE-SCHEMA.md` - Testing (containers only): `docs/TESTING.md` - Database Migration: `docs/DATABASE-MIGRATION.md` -- Admin feature: `docs/ADMIN.md` - Role management, APIs, catalog CRUD, station oversight - Development Environment: `docker-compose.yml` - Application features (start at each README): - `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/vehicles/README.md` - User vehicle management + - `backend/src/features/auth/README.md` - User signup and email verification + - `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/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/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 - Canonical URLs: Frontend `https://motovaultpro.com`, Backend health `https://motovaultpro.com/api/health`. -- All 7 features have comprehensive test suites (unit + integration tests). \ No newline at end of file +- All 15 features have comprehensive test suites (unit + integration tests). diff --git a/docs/TESTING.md b/docs/TESTING.md index 7605b16..a3a3cd0 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -176,13 +176,21 @@ make clean && make start - **Seeding**: Use feature-level fixtures when needed ### 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 +- `auth` - Unit + integration tests +- `backup` - Unit + integration tests - `documents` - Unit + integration tests - `fuel-logs` - Unit + integration tests - `maintenance` - Unit + integration tests +- `notifications` - Unit + integration tests +- `onboarding` - Unit tests - `platform` - Unit + integration tests - `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 ### Mock Strategy diff --git a/docs/VEHICLES-API.md b/docs/VEHICLES-API.md index 4e90e7d..1ae466c 100644 --- a/docs/VEHICLES-API.md +++ b/docs/VEHICLES-API.md @@ -1,167 +1,434 @@ -# Vehicles API – Platform Rebuild, App Integration, and Operations +# Vehicles API Reference -This document explains the end‑to‑end 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 production‑only development. +Complete API documentation for the vehicles feature in MotoVaultPro. ## 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, production‑only workflow, AI‑friendly 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`) -- `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` +**Authentication**: All endpoints require JWT authentication via `Authorization: Bearer ` header. -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` -- `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`) +``` +https://motovaultpro.com/api +``` -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) -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}` +### List User Vehicles -### Authentication -- 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 +Returns all vehicles for the authenticated user. -### 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}` -- 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` +``` +GET /api/vehicles +``` -### Seeds & Specific Examples -Platform seed migrations (TypeScript backend): -- 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`) -Seeds are auto-migrated on backend container start via `backend/src/_system/migrations/run-all.ts`. +**Response** (200): +```json +[ + { + "id": "uuid", + "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: -- `docker compose exec -T mvp-redis sh -lc "redis-cli FLUSHALL"` -- Or restart containers: `make rebuild` +### Get Single Vehicle -## MotoVaultPro Backend (Application Service) +``` +GET /api/vehicles/:id +``` -### Proxy Dropdown Endpoints -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. +**Parameters**: +- `id` (path): Vehicle UUID -### Platform Integration -- `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. +**Response** (200): Single vehicle object (same structure as list item) -### Authentication (App) -- Auth0 JWT enforced via Fastify + JWKS. No mock users. +**Errors**: +- `404`: Vehicle not found or not owned by user -### Migrations (Production‑Quality) -- 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 -- Auto‑migrate on backend container start: `node dist/_system/migrations/run-all.js && npm start` -- Manual: `make migrate` (runs runner inside the container) +### Create Vehicle -## Frontend Changes -- Vehicles form cascades: year → make → model → trim → engine. - - Engines load only after a trim is selected (requires `trim_id`). -- Validation updated: user must provide either a 17‑char VIN or a non‑empty license plate. - - VIN Decode button still requires a valid 17‑char VIN. -- APIs used: - - `/api/vehicles/dropdown/years` - - `/api/vehicles/dropdown/makes|models|trims|engines` - - `/api/vehicles/dropdown/transmissions` +``` +POST /api/vehicles +``` -## 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 -- 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` (auto‑migrated on backend start) +**Required**: Either `vin` OR `licensePlate` must be provided (not both required). -Add a new field (example: bodyStyle) -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. +**Response** (201): Created vehicle object -Modify an existing field -- 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. +### Update Vehicle -Delete a field (safe path) -- Remove from VehicleForm and frontend types. -- Remove from backend types/repository mapping. -- Optional migration to drop the column later. +``` +PUT /api/vehicles/:id +``` -Dropdown ordering -- Implemented in VehicleForm; current order is Year → Make → Model → Trim → Engine → Transmission (static). -- Engine select is enabled only after a Trim is selected. +**Parameters**: +- `id` (path): Vehicle UUID -VIN/License rule -- Frontend Zod: either 17‑char VIN or non‑empty 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. +**Request Body**: Same fields as create (all optional, only provided fields are updated) -## Operations +**Response** (200): Updated vehicle object -### Rebuild a single service -- Frontend: `docker compose up -d --build frontend` -- Backend (includes platform module): `docker compose up -d --build backend` +### Delete Vehicle -### Logs & Health -- Backend: `https://motovaultpro.com/api/health` – shows status/feature list, including platform readiness -- Logs: `make logs-backend`, `make logs-frontend` +Soft-deletes a vehicle (sets `deleted_at` timestamp). -### Common Reset Sequences -- Platform seed reapply (non‑destructive): apply `005_seed_specific_vehicles.sql` and flush Redis cache. -- 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 +``` +DELETE /api/vehicles/:id +``` -## Security Summary -- Platform Module: Auth0 JWT via `Authorization: Bearer ${JWT_TOKEN}` required on all `/api/platform/*` endpoints. -- Vehicles Feature: Auth0 JWT required on all protected `/api/vehicles/*` routes. -- Health Check: `/api/health` is unauthenticated (Traefik readiness probe). +**Parameters**: +- `id` (path): Vehicle UUID -## CI Summary -- Workflow `.github/workflows/ci.yml` builds backend/frontend/platform API. -- Runs backend lint/tests in a builder image on a stable network. +**Response** (204): No content -## Troubleshooting -- 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 auto‑migrate 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. +## Vehicle Images -## Notable Files -- Platform schema & seeds: maintained by database admins (legacy FastAPI scripts available on request) -- Platform API integration: `backend/src/features/platform/api/*` -- 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` -- Frontend vehicles UI: `frontend/src/features/vehicles/*` +### Upload Image + +Upload or replace the vehicle's image. + +``` +POST /api/vehicles/:id/image +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`