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
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:
104
.ai/context.json
104
.ai/context.json
@@ -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": {
|
||||||
|
|||||||
135
backend/src/features/user-preferences/README.md
Normal file
135
backend/src/features/user-preferences/README.md
Normal 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.
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
## 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`)
|
**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 (Production‑Quality)
|
### 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
|
|
||||||
- Auto‑migrate 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 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`
|
|
||||||
|
|
||||||
## 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` (auto‑migrated 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 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.
|
|
||||||
|
|
||||||
## 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 (non‑destructive): 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 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.
|
|
||||||
|
|
||||||
## 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`
|
||||||
|
|||||||
Reference in New Issue
Block a user