Pre-web changes
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
# Development Environment Variables
|
||||
# This file is for local development only - NOT for production k8s deployment
|
||||
# In k8s, these values come from ConfigMaps and Secrets
|
||||
|
||||
# Frontend Vite Configuration (build-time only)
|
||||
VITE_AUTH0_DOMAIN=motovaultpro.us.auth0.com
|
||||
VITE_AUTH0_CLIENT_ID=yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3
|
||||
VITE_AUTH0_AUDIENCE=https://api.motovaultpro.com
|
||||
VITE_API_BASE_URL=/api
|
||||
|
||||
# Docker Compose Development Configuration
|
||||
# These variables are used by docker-compose for container build args only
|
||||
AUTH0_DOMAIN=motovaultpro.us.auth0.com
|
||||
AUTH0_CLIENT_ID=yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3
|
||||
AUTH0_AUDIENCE=https://api.motovaultpro.com
|
||||
|
||||
# NOTE: Backend services no longer use this file
|
||||
# Backend configuration comes from:
|
||||
# - /app/config/production.yml (non-sensitive config)
|
||||
# - /run/secrets/ (sensitive secrets)
|
||||
@@ -278,12 +278,12 @@ Per CLAUDE.md, all code must pass:
|
||||
|
||||
```bash
|
||||
# Development
|
||||
make setup # Build + start + migrate
|
||||
make rebuild # Rebuild containers
|
||||
make logs # Tail all logs
|
||||
make test # Run all tests
|
||||
make start # Start services
|
||||
make migrate # Run migrations
|
||||
make setup # Build + start + migrate
|
||||
make rebuild # Rebuild containers
|
||||
make logs # Tail all logs
|
||||
docker compose exec mvp-backend npm test # Run backend test suite inside container
|
||||
make start # Start services
|
||||
make migrate # Run migrations
|
||||
|
||||
# Testing
|
||||
cd backend && npm test -- features/stations
|
||||
@@ -299,7 +299,7 @@ echo "YOUR_API_KEY" > ./secrets/app/google-maps-api-key.txt
|
||||
|
||||
# Verification
|
||||
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
|
||||
curl -H "Authorization: Bearer $TOKEN" http://localhost:3001/api/stations/saved
|
||||
curl -H "Authorization: Bearer $TOKEN" https://motovaultpro.com/api/stations/saved
|
||||
```
|
||||
|
||||
## Final Notes
|
||||
|
||||
20
Makefile
20
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: help setup start stop clean test test-frontend logs shell-backend shell-frontend migrate rebuild traefik-dashboard traefik-logs service-discovery network-inspect health-check-all mobile-setup db-shell-app
|
||||
.PHONY: help setup start stop clean logs shell-backend shell-frontend migrate rebuild traefik-dashboard traefik-logs service-discovery network-inspect health-check-all mobile-setup db-shell-app
|
||||
|
||||
help:
|
||||
@echo "MotoVaultPro - Simplified 5-Container Architecture"
|
||||
@@ -46,7 +46,7 @@ setup:
|
||||
@echo ""
|
||||
@echo "K8s-ready setup complete!"
|
||||
@echo "Access application at: https://motovaultpro.com"
|
||||
@echo "Traefik dashboard at: http://localhost:8080"
|
||||
@echo "Traefik dashboard at: https://motovaultpro.com:8080"
|
||||
@echo ""
|
||||
@echo "Network Architecture:"
|
||||
@echo " - 3-tier isolation: frontend, backend, database"
|
||||
@@ -101,11 +101,11 @@ db-shell-app:
|
||||
# K8s-Ready Architecture Commands
|
||||
traefik-dashboard:
|
||||
@echo "Traefik Service Discovery Dashboard:"
|
||||
@echo " Dashboard: http://localhost:8080"
|
||||
@echo " API: http://localhost:8080/api"
|
||||
@echo " Dashboard: https://motovaultpro.com:8080"
|
||||
@echo " API: https://motovaultpro.com:8080/api"
|
||||
@echo ""
|
||||
@echo "Available routes:"
|
||||
@curl -s http://localhost:8080/api/http/routers 2>/dev/null | jq -r '.[].name' | grep -v internal | sed 's/^/ - /' || echo " (Traefik not ready yet)"
|
||||
@curl -ks https://motovaultpro.com:8080/api/http/routers 2>/dev/null | jq -r '.[].name' | grep -v internal | sed 's/^/ - /' || echo " (Traefik not ready yet)"
|
||||
|
||||
traefik-logs:
|
||||
@echo "Traefik access and error logs:"
|
||||
@@ -116,10 +116,10 @@ service-discovery:
|
||||
@echo "Service Discovery Status:"
|
||||
@echo ""
|
||||
@echo "Discovered Services:"
|
||||
@curl -s http://localhost:8080/api/http/services 2>/dev/null | jq -r '.[].name' | grep -v internal | sed 's/^/ - /' || echo " Traefik not ready yet"
|
||||
@curl -ks https://motovaultpro.com:8080/api/http/services 2>/dev/null | jq -r '.[].name' | grep -v internal | sed 's/^/ - /' || echo " Traefik not ready yet"
|
||||
@echo ""
|
||||
@echo "Active Routes:"
|
||||
@curl -s http://localhost:8080/api/http/routers 2>/dev/null | jq -r '.[].name' | grep -v internal | sed 's/^/ -> /' || echo " No routes discovered yet"
|
||||
@curl -ks https://motovaultpro.com:8080/api/http/routers 2>/dev/null | jq -r '.[].name' | grep -v internal | sed 's/^/ -> /' || echo " No routes discovered yet"
|
||||
|
||||
network-inspect:
|
||||
@echo "K8s-Ready Network Architecture:"
|
||||
@@ -137,11 +137,11 @@ health-check-all:
|
||||
@docker compose ps --format "table {{.Service}}\t{{.Status}}\t{{.Health}}"
|
||||
@echo ""
|
||||
@echo "Network Connectivity Test:"
|
||||
@echo " Traefik API: $$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/api/http/services 2>/dev/null || echo 'FAIL')"
|
||||
@echo " Traefik API: $$(curl -ks -o /dev/null -w '%{http_code}' https://motovaultpro.com:8080/api/http/services 2>/dev/null || echo 'FAIL')"
|
||||
@echo ""
|
||||
@echo "Service Discovery Status:"
|
||||
@echo " Discovered Services: $$(curl -s http://localhost:8080/api/http/services 2>/dev/null | jq '. | length' || echo '0')"
|
||||
@echo " Active Routes: $$(curl -s http://localhost:8080/api/http/routers 2>/dev/null | jq '. | length' || echo '0')"
|
||||
@echo " Discovered Services: $$(curl -ks https://motovaultpro.com:8080/api/http/services 2>/dev/null | jq '. | length' || echo '0')"
|
||||
@echo " Active Routes: $$(curl -ks https://motovaultpro.com:8080/api/http/routers 2>/dev/null | jq '. | length' || echo '0')"
|
||||
|
||||
# SSL Certificate Generation
|
||||
generate-certs:
|
||||
|
||||
@@ -7,6 +7,7 @@ import pool from '../../core/config/database';
|
||||
import { cacheService } from '../../core/config/redis';
|
||||
import { VINDecodeService } from './domain/vin-decode.service';
|
||||
import { PlatformCacheService } from './domain/platform-cache.service';
|
||||
import { VehicleDataService } from './domain/vehicle-data.service';
|
||||
|
||||
export { platformRoutes } from './api/platform.routes';
|
||||
export { PlatformController } from './api/platform.controller';
|
||||
@@ -18,6 +19,7 @@ export * from './models/responses';
|
||||
|
||||
// Singleton VIN decode service for use by other features
|
||||
let vinDecodeServiceInstance: VINDecodeService | null = null;
|
||||
let vehicleDataServiceInstance: VehicleDataService | null = null;
|
||||
|
||||
export function getVINDecodeService(): VINDecodeService {
|
||||
if (!vinDecodeServiceInstance) {
|
||||
@@ -27,6 +29,14 @@ export function getVINDecodeService(): VINDecodeService {
|
||||
return vinDecodeServiceInstance;
|
||||
}
|
||||
|
||||
export function getVehicleDataService(): VehicleDataService {
|
||||
if (!vehicleDataServiceInstance) {
|
||||
const platformCache = new PlatformCacheService(cacheService);
|
||||
vehicleDataServiceInstance = new VehicleDataService(platformCache);
|
||||
}
|
||||
return vehicleDataServiceInstance;
|
||||
}
|
||||
|
||||
// Helper to get pool for VIN decode service
|
||||
export function getPool(): Pool {
|
||||
return pool;
|
||||
|
||||
@@ -203,7 +203,7 @@ make setup
|
||||
make logs
|
||||
|
||||
# Verify
|
||||
curl http://localhost:3001/health
|
||||
curl https://motovaultpro.com/api/health
|
||||
```
|
||||
|
||||
### Verification
|
||||
@@ -215,7 +215,7 @@ docker compose exec mvp-frontend cat /run/secrets/google-maps-api-key
|
||||
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
|
||||
|
||||
# Test API endpoint
|
||||
curl -H "Authorization: Bearer $TOKEN" http://localhost:3001/api/stations/saved
|
||||
curl -H "Authorization: Bearer $TOKEN" https://motovaultpro.com/api/stations/saved
|
||||
```
|
||||
|
||||
## Next Steps (Phases 6-11)
|
||||
|
||||
@@ -19,7 +19,7 @@ Authorization: Bearer {jwt_token}
|
||||
|
||||
## Base URL
|
||||
|
||||
**Development**: `http://localhost:3001/api/stations`
|
||||
**Development**: `https://motovaultpro.com/api/stations`
|
||||
|
||||
**Production**: `https://motovaultpro.com/api/stations`
|
||||
|
||||
@@ -574,7 +574,7 @@ TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
|
||||
```bash
|
||||
# 1. Search for stations
|
||||
curl -X POST http://localhost:3001/api/stations/search \
|
||||
curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -585,7 +585,7 @@ curl -X POST http://localhost:3001/api/stations/search \
|
||||
|
||||
# 2. Save a station from search results
|
||||
PLACE_ID="ChIJN1t_tDeuEmsRUsoyG83frY4"
|
||||
curl -X POST http://localhost:3001/api/stations/save \
|
||||
curl -X POST https://motovaultpro.com/api/stations/save \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
@@ -595,11 +595,11 @@ curl -X POST http://localhost:3001/api/stations/save \
|
||||
}" | jq
|
||||
|
||||
# 3. Get all saved stations
|
||||
curl -X GET http://localhost:3001/api/stations/saved \
|
||||
curl -X GET https://motovaultpro.com/api/stations/saved \
|
||||
-H "Authorization: Bearer $TOKEN" | jq
|
||||
|
||||
# 4. Update saved station
|
||||
curl -X PATCH http://localhost:3001/api/stations/saved/$PLACE_ID \
|
||||
curl -X PATCH https://motovaultpro.com/api/stations/saved/$PLACE_ID \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -607,7 +607,7 @@ curl -X PATCH http://localhost:3001/api/stations/saved/$PLACE_ID \
|
||||
}' | jq
|
||||
|
||||
# 5. Delete saved station
|
||||
curl -X DELETE http://localhost:3001/api/stations/saved/$PLACE_ID \
|
||||
curl -X DELETE https://motovaultpro.com/api/stations/saved/$PLACE_ID \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
@@ -644,7 +644,7 @@ Google Maps API key is loaded from `/run/secrets/google-maps-api-key` at contain
|
||||
|
||||
API endpoints are configured to accept requests from:
|
||||
- https://motovaultpro.com (production)
|
||||
- http://localhost:3000 (development)
|
||||
- https://motovaultpro.com (development)
|
||||
|
||||
## Versioning
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ The Gas Stations feature requires two Google Maps APIs:
|
||||
- Add allowed domains:
|
||||
```
|
||||
https://motovaultpro.com/*
|
||||
http://localhost:3000/* # Development only
|
||||
https://motovaultpro.com/* # Development only
|
||||
```
|
||||
3. Click **Save**
|
||||
|
||||
@@ -273,7 +273,7 @@ Cost:
|
||||
|
||||
```bash
|
||||
# Development
|
||||
secrets/app/google-maps-api-key.txt → Development key (restricted to localhost)
|
||||
secrets/app/google-maps-api-key.txt → Development key (restricted to motovaultpro.com)
|
||||
|
||||
# Production
|
||||
Kubernetes secret → Production key (restricted to production IPs)
|
||||
@@ -421,7 +421,7 @@ After setup, verify everything works:
|
||||
- [ ] Billing alerts configured
|
||||
- [ ] Key tested in development:
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/stations/search \
|
||||
curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}'
|
||||
|
||||
@@ -275,7 +275,7 @@ docker compose ps
|
||||
# All should show "Up" status
|
||||
|
||||
# Check backend health
|
||||
curl -s http://localhost:3001/health | jq
|
||||
curl -s https://motovaultpro.com/api/health | jq
|
||||
# Should return: {"status":"ok","features":["stations",...]}
|
||||
|
||||
# Check frontend loads
|
||||
@@ -292,11 +292,11 @@ Run comprehensive health checks to verify deployment:
|
||||
**Backend Health**:
|
||||
```bash
|
||||
# Overall health check
|
||||
curl http://localhost:3001/health
|
||||
curl https://motovaultpro.com/api/health
|
||||
|
||||
# Stations feature health (implicit in API availability)
|
||||
curl -H "Authorization: Bearer $TEST_TOKEN" \
|
||||
http://localhost:3001/api/stations/saved
|
||||
https://motovaultpro.com/api/stations/saved
|
||||
```
|
||||
|
||||
**Frontend Health**:
|
||||
@@ -343,7 +343,7 @@ export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
|
||||
**Test Search Endpoint**:
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/stations/search \
|
||||
curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -379,14 +379,14 @@ curl -X POST http://localhost:3001/api/stations/search \
|
||||
**Test Save Endpoint**:
|
||||
```bash
|
||||
# First search to populate cache
|
||||
PLACE_ID=$(curl -X POST http://localhost:3001/api/stations/search \
|
||||
PLACE_ID=$(curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}' | \
|
||||
jq -r '.stations[0].placeId')
|
||||
|
||||
# Then save station
|
||||
curl -X POST http://localhost:3001/api/stations/save \
|
||||
curl -X POST https://motovaultpro.com/api/stations/save \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
@@ -398,13 +398,13 @@ curl -X POST http://localhost:3001/api/stations/save \
|
||||
|
||||
**Test Get Saved Endpoint**:
|
||||
```bash
|
||||
curl -X GET http://localhost:3001/api/stations/saved \
|
||||
curl -X GET https://motovaultpro.com/api/stations/saved \
|
||||
-H "Authorization: Bearer $TOKEN" | jq
|
||||
```
|
||||
|
||||
**Test Delete Endpoint**:
|
||||
```bash
|
||||
curl -X DELETE http://localhost:3001/api/stations/saved/$PLACE_ID \
|
||||
curl -X DELETE https://motovaultpro.com/api/stations/saved/$PLACE_ID \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
# Should return 204 No Content
|
||||
```
|
||||
@@ -437,20 +437,20 @@ Verify users can only access their own data:
|
||||
```bash
|
||||
# User 1 saves a station
|
||||
USER1_TOKEN="..."
|
||||
PLACE_ID=$(curl -X POST http://localhost:3001/api/stations/search \
|
||||
PLACE_ID=$(curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $USER1_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}' | \
|
||||
jq -r '.stations[0].placeId')
|
||||
|
||||
curl -X POST http://localhost:3001/api/stations/save \
|
||||
curl -X POST https://motovaultpro.com/api/stations/save \
|
||||
-H "Authorization: Bearer $USER1_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"placeId\": \"$PLACE_ID\"}"
|
||||
|
||||
# User 2 should NOT see User 1's saved station
|
||||
USER2_TOKEN="..."
|
||||
curl -X GET http://localhost:3001/api/stations/saved \
|
||||
curl -X GET https://motovaultpro.com/api/stations/saved \
|
||||
-H "Authorization: Bearer $USER2_TOKEN" | jq
|
||||
# Should return: [] (empty array)
|
||||
```
|
||||
@@ -461,13 +461,13 @@ Test response times meet requirements:
|
||||
|
||||
```bash
|
||||
# Search endpoint (should be < 1500ms)
|
||||
time curl -X POST http://localhost:3001/api/stations/search \
|
||||
time curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}'
|
||||
|
||||
# Saved stations endpoint (should be < 100ms)
|
||||
time curl -X GET http://localhost:3001/api/stations/saved \
|
||||
time curl -X GET https://motovaultpro.com/api/stations/saved \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
@@ -532,7 +532,7 @@ docker compose exec postgres psql -U postgres -d motovaultpro < backup_YYYYMMDD_
|
||||
docker compose up -d
|
||||
|
||||
# Verify rollback successful
|
||||
curl http://localhost:3001/health
|
||||
curl https://motovaultpro.com/api/health
|
||||
```
|
||||
|
||||
### Partial Rollback (Disable Feature)
|
||||
@@ -547,7 +547,7 @@ If only stations feature needs to be disabled:
|
||||
docker compose up -d --build mvp-backend
|
||||
|
||||
# Verify other features still work
|
||||
curl http://localhost:3001/health
|
||||
curl https://motovaultpro.com/api/health
|
||||
```
|
||||
|
||||
### Database Rollback Only
|
||||
@@ -655,7 +655,7 @@ docker compose logs mvp-backend | grep -i migration
|
||||
|
||||
- [ ] Check container health: `docker compose ps`
|
||||
- [ ] Review error logs: `docker compose logs | grep -i error`
|
||||
- [ ] Verify API responding: `curl http://localhost:3001/health`
|
||||
- [ ] Verify API responding: `curl https://motovaultpro.com/api/health`
|
||||
|
||||
### Weekly Checks
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Run this single command for immediate status:
|
||||
|
||||
```bash
|
||||
# All-in-one health check
|
||||
curl -s http://localhost:3001/health | jq
|
||||
curl -s https://motovaultpro.com/api/health | jq
|
||||
```
|
||||
|
||||
**Expected Output**:
|
||||
@@ -39,7 +39,7 @@ export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
#### Test Search Endpoint
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/stations/search \
|
||||
curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -61,14 +61,14 @@ curl -X POST http://localhost:3001/api/stations/search \
|
||||
|
||||
```bash
|
||||
# First search to get a place ID
|
||||
PLACE_ID=$(curl -s -X POST http://localhost:3001/api/stations/search \
|
||||
PLACE_ID=$(curl -s -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}' \
|
||||
| jq -r '.stations[0].placeId')
|
||||
|
||||
# Then save station
|
||||
curl -X POST http://localhost:3001/api/stations/save \
|
||||
curl -X POST https://motovaultpro.com/api/stations/save \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"placeId\": \"$PLACE_ID\", \"nickname\": \"Health Check Station\"}" \
|
||||
@@ -84,7 +84,7 @@ curl -X POST http://localhost:3001/api/stations/save \
|
||||
#### Test Get Saved Endpoint
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:3001/api/stations/saved \
|
||||
curl -X GET https://motovaultpro.com/api/stations/saved \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-w "\nHTTP Status: %{http_code}\nTime: %{time_total}s\n" \
|
||||
| jq
|
||||
@@ -98,7 +98,7 @@ curl -X GET http://localhost:3001/api/stations/saved \
|
||||
#### Test Delete Endpoint
|
||||
|
||||
```bash
|
||||
curl -X DELETE http://localhost:3001/api/stations/saved/$PLACE_ID \
|
||||
curl -X DELETE https://motovaultpro.com/api/stations/saved/$PLACE_ID \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-w "\nHTTP Status: %{http_code}\nTime: %{time_total}s\n"
|
||||
```
|
||||
@@ -316,13 +316,13 @@ Verify users can only access their own data:
|
||||
USER1_TOKEN="..."
|
||||
|
||||
# User 1 saves a station
|
||||
PLACE_ID=$(curl -s -X POST http://localhost:3001/api/stations/search \
|
||||
PLACE_ID=$(curl -s -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $USER1_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}' \
|
||||
| jq -r '.stations[0].placeId')
|
||||
|
||||
curl -s -X POST http://localhost:3001/api/stations/save \
|
||||
curl -s -X POST https://motovaultpro.com/api/stations/save \
|
||||
-H "Authorization: Bearer $USER1_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"placeId\": \"$PLACE_ID\"}" > /dev/null
|
||||
@@ -331,7 +331,7 @@ curl -s -X POST http://localhost:3001/api/stations/save \
|
||||
USER2_TOKEN="..."
|
||||
|
||||
# User 2 tries to access User 1's saved stations
|
||||
curl -s -X GET http://localhost:3001/api/stations/saved \
|
||||
curl -s -X GET https://motovaultpro.com/api/stations/saved \
|
||||
-H "Authorization: Bearer $USER2_TOKEN" \
|
||||
| jq '. | length'
|
||||
```
|
||||
@@ -346,7 +346,7 @@ curl -s -X GET http://localhost:3001/api/stations/saved \
|
||||
# Run 10 searches and measure average time
|
||||
for i in {1..10}; do
|
||||
curl -s -o /dev/null -w "%{time_total}\n" \
|
||||
-X POST http://localhost:3001/api/stations/search \
|
||||
-X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}'
|
||||
@@ -360,7 +360,7 @@ done | awk '{sum+=$1} END {print "Average:", sum/NR, "seconds"}'
|
||||
```bash
|
||||
# Measure save operation time
|
||||
time curl -s -o /dev/null \
|
||||
-X POST http://localhost:3001/api/stations/save \
|
||||
-X POST https://motovaultpro.com/api/stations/save \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"placeId\": \"$PLACE_ID\"}"
|
||||
@@ -379,7 +379,7 @@ sudo apt-get install apache2-utils
|
||||
# Run load test (100 requests, 10 concurrent)
|
||||
ab -n 100 -c 10 -T 'application/json' -H "Authorization: Bearer $TOKEN" \
|
||||
-p search_payload.json \
|
||||
http://localhost:3001/api/stations/search
|
||||
https://motovaultpro.com/api/stations/search
|
||||
```
|
||||
|
||||
**Expected**:
|
||||
@@ -415,7 +415,7 @@ if [ -z "$TOKEN" ]; then
|
||||
fi
|
||||
|
||||
echo "1. Backend Health Check..."
|
||||
if curl -s http://localhost:3001/health | jq -e '.status == "ok"' > /dev/null; then
|
||||
if curl -s https://motovaultpro.com/api/health | jq -e '.status == "ok"' > /dev/null; then
|
||||
echo "${GREEN}✓ Backend healthy${NC}"
|
||||
else
|
||||
echo "${RED}✗ Backend unhealthy${NC}"
|
||||
@@ -444,7 +444,7 @@ fi
|
||||
|
||||
echo ""
|
||||
echo "4. Search API Check..."
|
||||
if curl -s -X POST http://localhost:3001/api/stations/search \
|
||||
if curl -s -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}' \
|
||||
@@ -492,7 +492,7 @@ JWT_TOKEN="your_token_here" ./health-check.sh
|
||||
- [ ] Backend container running: `docker compose ps mvp-backend`
|
||||
- [ ] Frontend container running: `docker compose ps mvp-frontend`
|
||||
- [ ] No errors in logs: `docker compose logs --tail=100 | grep -i error`
|
||||
- [ ] Health endpoint responding: `curl http://localhost:3001/health`
|
||||
- [ ] Health endpoint responding: `curl https://motovaultpro.com/api/health`
|
||||
|
||||
### Weekly Monitoring
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ Complete production readiness checklist for the Gas Stations feature. This docum
|
||||
|
||||
#### Immediate Validation (within 5 minutes)
|
||||
|
||||
- [ ] Health endpoint responding: `curl http://localhost:3001/health`
|
||||
- [ ] Health endpoint responding: `curl https://motovaultpro.com/api/health`
|
||||
- [ ] Backend logs show no errors: `docker compose logs mvp-backend | grep -i error`
|
||||
- [ ] Frontend logs show no errors: `docker compose logs mvp-frontend | grep -i error`
|
||||
- [ ] Database tables exist: `\dt station*`
|
||||
|
||||
@@ -140,7 +140,7 @@ docker compose logs mvp-frontend | grep -i config
|
||||
|
||||
**Check config.js is served**:
|
||||
```bash
|
||||
curl -s http://localhost:3000/config.js
|
||||
curl -s https://motovaultpro.com/config.js
|
||||
```
|
||||
|
||||
**Expected**: JavaScript file with `window.CONFIG = {...}`
|
||||
@@ -214,7 +214,7 @@ Results: 20 stations
|
||||
export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
|
||||
# Search for stations
|
||||
curl -X POST http://localhost:3001/api/stations/search \
|
||||
curl -X POST https://motovaultpro.com/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
@@ -400,7 +400,7 @@ docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
|
||||
|
||||
2. **Check config.js content**:
|
||||
```bash
|
||||
curl http://localhost:3000/config.js
|
||||
curl https://motovaultpro.com/config.js
|
||||
```
|
||||
|
||||
3. **Check index.html loads config.js**:
|
||||
|
||||
@@ -13,16 +13,17 @@ Primary entity for vehicle management consuming MVP Platform Vehicles Service. H
|
||||
- `DELETE /api/vehicles/:id` - Soft delete vehicle
|
||||
|
||||
### Hierarchical Vehicle Dropdowns
|
||||
**Status**: Dropdown methods are TODO stubs in vehicles service. Frontend directly consumes platform module endpoints.
|
||||
**Status**: Vehicles service now proxies the platform vehicle catalog to provide fully dynamic dropdowns. Each selection step filters the next list, ensuring only valid combinations are shown.
|
||||
|
||||
Frontend consumes (via `/platform` module, not vehicles feature):
|
||||
- `GET /api/platform/years` - Get all years
|
||||
- `GET /api/platform/makes?year={year}` - Get makes for year
|
||||
- `GET /api/platform/models?year={year}&make_id={make_id}` - Get models for make/year
|
||||
- `GET /api/platform/trims?year={year}&make_id={make_id}&model_id={model_id}` - Get trims
|
||||
- `GET /api/platform/engines?year={year}&make_id={make_id}&model_id={model_id}&trim_id={trim_id}` - Get engines
|
||||
- `GET /api/platform/transmissions?year={year}&make_id={make_id}&model_id={model_id}` - Get transmissions
|
||||
- `GET /api/platform/vehicle?vin={vin}` - Decode VIN
|
||||
Sequence:
|
||||
1. `GET /api/vehicles/dropdown/years` → `[number]` (latest to oldest).
|
||||
2. `GET /api/vehicles/dropdown/makes?year={year}` → `{ id, name }[]` (only makes produced in the selected year).
|
||||
3. `GET /api/vehicles/dropdown/models?year={year}&make_id={id}` → `{ id, name }[]` (models offered for that year/make).
|
||||
4. `GET /api/vehicles/dropdown/trims?year={year}&make_id={id}&model_id={id}` → `{ id, name }[]` (valid trims for the chosen combination).
|
||||
5. `GET /api/vehicles/dropdown/engines?year={year}&make_id={id}&model_id={id}&trim_id={id}` → `{ id, name }[]` (engines tied to the trim).
|
||||
6. `GET /api/vehicles/dropdown/transmissions?year={year}&make_id={id}&model_id={id}` → `{ id, name }[]` (static options: Automatic, Manual).
|
||||
|
||||
All dropdown endpoints call `Platform VehicleDataService` behind the scenes, reuse Redis caching, and return normalized `{ id, name }` payloads ready for the frontend.
|
||||
|
||||
## Authentication
|
||||
- All vehicles endpoints (including dropdowns) require a valid JWT (Auth0).
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { VehiclesRepository } from '../data/vehicles.repository';
|
||||
import { getVINDecodeService, getPool } from '../../platform';
|
||||
import { getVINDecodeService, getVehicleDataService, getPool } from '../../platform';
|
||||
import {
|
||||
Vehicle,
|
||||
CreateVehicleRequest,
|
||||
@@ -162,52 +162,52 @@ export class VehiclesService {
|
||||
await cacheService.del(cacheKey);
|
||||
}
|
||||
|
||||
async getDropdownMakes(_year: number): Promise<{ id: number; name: string }[]> {
|
||||
// TODO: Implement using platform VehicleDataService
|
||||
// For now, return empty array to allow migration to complete
|
||||
logger.warn('Dropdown makes not yet implemented via platform feature');
|
||||
return [];
|
||||
async getDropdownMakes(year: number): Promise<{ id: number; name: string }[]> {
|
||||
const vehicleDataService = getVehicleDataService();
|
||||
const pool = getPool();
|
||||
|
||||
logger.info('Fetching dropdown makes via platform module', { year });
|
||||
return vehicleDataService.getMakes(pool, year);
|
||||
}
|
||||
|
||||
async getDropdownModels(_year: number, _makeId: number): Promise<{ id: number; name: string }[]> {
|
||||
// TODO: Implement using platform VehicleDataService
|
||||
logger.warn('Dropdown models not yet implemented via platform feature');
|
||||
return [];
|
||||
async getDropdownModels(year: number, makeId: number): Promise<{ id: number; name: string }[]> {
|
||||
const vehicleDataService = getVehicleDataService();
|
||||
const pool = getPool();
|
||||
|
||||
logger.info('Fetching dropdown models via platform module', { year, makeId });
|
||||
return vehicleDataService.getModels(pool, year, makeId);
|
||||
}
|
||||
|
||||
async getDropdownTransmissions(_year: number, _makeId: number, _modelId: number): Promise<{ name: string }[]> {
|
||||
// TODO: Implement using platform VehicleDataService
|
||||
logger.warn('Dropdown transmissions not yet implemented via platform feature');
|
||||
return [];
|
||||
async getDropdownTransmissions(_year: number, _makeId: number, _modelId: number): Promise<{ id: number; name: string }[]> {
|
||||
logger.info('Providing dropdown transmissions from static list');
|
||||
return [
|
||||
{ id: 1, name: 'Automatic' },
|
||||
{ id: 2, name: 'Manual' }
|
||||
];
|
||||
}
|
||||
|
||||
async getDropdownEngines(_year: number, _makeId: number, _modelId: number, _trimId: number): Promise<{ name: string }[]> {
|
||||
// TODO: Implement using platform VehicleDataService
|
||||
logger.warn('Dropdown engines not yet implemented via platform feature');
|
||||
return [];
|
||||
async getDropdownEngines(year: number, makeId: number, modelId: number, trimId: number): Promise<{ id: number; name: string }[]> {
|
||||
const vehicleDataService = getVehicleDataService();
|
||||
const pool = getPool();
|
||||
|
||||
logger.info('Fetching dropdown engines via platform module', { year, makeId, modelId, trimId });
|
||||
return vehicleDataService.getEngines(pool, year, modelId, trimId);
|
||||
}
|
||||
|
||||
async getDropdownTrims(_year: number, _makeId: number, _modelId: number): Promise<{ name: string }[]> {
|
||||
// TODO: Implement using platform VehicleDataService
|
||||
logger.warn('Dropdown trims not yet implemented via platform feature');
|
||||
return [];
|
||||
async getDropdownTrims(year: number, makeId: number, modelId: number): Promise<{ id: number; name: string }[]> {
|
||||
const vehicleDataService = getVehicleDataService();
|
||||
const pool = getPool();
|
||||
|
||||
logger.info('Fetching dropdown trims via platform module', { year, makeId, modelId });
|
||||
return vehicleDataService.getTrims(pool, year, modelId);
|
||||
}
|
||||
|
||||
async getDropdownYears(): Promise<number[]> {
|
||||
try {
|
||||
logger.info('Getting dropdown years');
|
||||
// Fallback: generate recent years
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years: number[] = [];
|
||||
for (let y = currentYear + 1; y >= 1980; y--) years.push(y);
|
||||
return years;
|
||||
} catch (error) {
|
||||
logger.error('Failed to get dropdown years', { error });
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years: number[] = [];
|
||||
for (let y = currentYear + 1; y >= 1980; y--) years.push(y);
|
||||
return years;
|
||||
}
|
||||
const vehicleDataService = getVehicleDataService();
|
||||
const pool = getPool();
|
||||
|
||||
logger.info('Fetching dropdown years via platform module');
|
||||
return vehicleDataService.getYears(pool);
|
||||
}
|
||||
|
||||
async decodeVIN(vin: string): Promise<{
|
||||
|
||||
@@ -12,20 +12,42 @@ import * as platformModule from '../../../platform';
|
||||
jest.mock('../../data/vehicles.repository');
|
||||
jest.mock('../../../../core/config/redis');
|
||||
jest.mock('../../../platform', () => ({
|
||||
getVINDecodeService: jest.fn()
|
||||
getVINDecodeService: jest.fn(),
|
||||
getVehicleDataService: jest.fn(),
|
||||
getPool: jest.fn()
|
||||
}));
|
||||
|
||||
const mockRepository = jest.mocked(VehiclesRepository);
|
||||
const mockCacheService = jest.mocked(cacheService);
|
||||
const mockGetVINDecodeService = jest.mocked(platformModule.getVINDecodeService);
|
||||
const mockGetVehicleDataService = jest.mocked(platformModule.getVehicleDataService);
|
||||
const mockGetPool = jest.mocked(platformModule.getPool);
|
||||
|
||||
describe('VehiclesService', () => {
|
||||
let service: VehiclesService;
|
||||
let repositoryInstance: jest.Mocked<VehiclesRepository>;
|
||||
let vehicleDataServiceMock: {
|
||||
getYears: jest.Mock;
|
||||
getMakes: jest.Mock;
|
||||
getModels: jest.Mock;
|
||||
getTrims: jest.Mock;
|
||||
getEngines: jest.Mock;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
vehicleDataServiceMock = {
|
||||
getYears: jest.fn(),
|
||||
getMakes: jest.fn(),
|
||||
getModels: jest.fn(),
|
||||
getTrims: jest.fn(),
|
||||
getEngines: jest.fn(),
|
||||
};
|
||||
|
||||
mockGetVehicleDataService.mockReturnValue(vehicleDataServiceMock as any);
|
||||
mockGetPool.mockReturnValue('mock-pool' as any);
|
||||
|
||||
repositoryInstance = {
|
||||
create: jest.fn(),
|
||||
findByUserId: jest.fn(),
|
||||
@@ -39,6 +61,63 @@ describe('VehiclesService', () => {
|
||||
service = new VehiclesService(repositoryInstance);
|
||||
});
|
||||
|
||||
describe('dropdown data integration', () => {
|
||||
it('retrieves years from platform service', async () => {
|
||||
vehicleDataServiceMock.getYears.mockResolvedValue([2024, 2023]);
|
||||
|
||||
const result = await service.getDropdownYears();
|
||||
|
||||
expect(mockGetVehicleDataService).toHaveBeenCalled();
|
||||
expect(vehicleDataServiceMock.getYears).toHaveBeenCalledWith('mock-pool');
|
||||
expect(result).toEqual([2024, 2023]);
|
||||
});
|
||||
|
||||
it('retrieves makes scoped to year', async () => {
|
||||
vehicleDataServiceMock.getMakes.mockResolvedValue([{ id: 10, name: 'Honda' }]);
|
||||
|
||||
const result = await service.getDropdownMakes(2024);
|
||||
|
||||
expect(vehicleDataServiceMock.getMakes).toHaveBeenCalledWith('mock-pool', 2024);
|
||||
expect(result).toEqual([{ id: 10, name: 'Honda' }]);
|
||||
});
|
||||
|
||||
it('retrieves models scoped to year and make', async () => {
|
||||
vehicleDataServiceMock.getModels.mockResolvedValue([{ id: 20, name: 'Civic' }]);
|
||||
|
||||
const result = await service.getDropdownModels(2024, 10);
|
||||
|
||||
expect(vehicleDataServiceMock.getModels).toHaveBeenCalledWith('mock-pool', 2024, 10);
|
||||
expect(result).toEqual([{ id: 20, name: 'Civic' }]);
|
||||
});
|
||||
|
||||
it('retrieves trims scoped to year, make, and model', async () => {
|
||||
vehicleDataServiceMock.getTrims.mockResolvedValue([{ id: 30, name: 'Sport' }]);
|
||||
|
||||
const result = await service.getDropdownTrims(2024, 10, 20);
|
||||
|
||||
expect(vehicleDataServiceMock.getTrims).toHaveBeenCalledWith('mock-pool', 2024, 20);
|
||||
expect(result).toEqual([{ id: 30, name: 'Sport' }]);
|
||||
});
|
||||
|
||||
it('retrieves engines scoped to selection', async () => {
|
||||
vehicleDataServiceMock.getEngines.mockResolvedValue([{ id: 40, name: '2.0L Turbo' }]);
|
||||
|
||||
const result = await service.getDropdownEngines(2024, 10, 20, 30);
|
||||
|
||||
expect(vehicleDataServiceMock.getEngines).toHaveBeenCalledWith('mock-pool', 2024, 20, 30);
|
||||
expect(result).toEqual([{ id: 40, name: '2.0L Turbo' }]);
|
||||
});
|
||||
|
||||
it('returns static transmission options', async () => {
|
||||
const result = await service.getDropdownTransmissions(2024, 10, 20);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'Automatic' },
|
||||
{ id: 2, name: 'Manual' }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('createVehicle', () => {
|
||||
const mockVehicleData = {
|
||||
vin: '1HGBH41JXMN109186',
|
||||
@@ -94,7 +173,7 @@ describe('VehiclesService', () => {
|
||||
const result = await service.createVehicle(mockVehicleData, 'user-123');
|
||||
|
||||
expect(repositoryInstance.findByUserAndVIN).toHaveBeenCalledWith('user-123', '1HGBH41JXMN109186');
|
||||
expect(mockVinDecodeService.decodeVIN).toHaveBeenCalledWith('1HGBH41JXMN109186');
|
||||
expect(mockVinDecodeService.decodeVIN).toHaveBeenCalledWith('mock-pool', '1HGBH41JXMN109186');
|
||||
expect(repositoryInstance.create).toHaveBeenCalledWith({
|
||||
...mockVehicleData,
|
||||
userId: 'user-123',
|
||||
|
||||
@@ -137,7 +137,7 @@ MotoVaultPro is a single-tenant vehicle management application built with a **6-
|
||||
- **Port**: 3000 (internal)
|
||||
- **Networks**: frontend
|
||||
- **Dependencies**: Backend (API calls)
|
||||
- **Health Check**: `curl http://localhost:3000` (30s interval)
|
||||
- **Health Check**: `curl https://motovaultpro.com` (30s interval)
|
||||
- **Environment Variables**:
|
||||
- `VITE_AUTH0_DOMAIN` - Auth0 tenant
|
||||
- `VITE_AUTH0_CLIENT_ID` - Auth0 application ID
|
||||
@@ -151,7 +151,7 @@ MotoVaultPro is a single-tenant vehicle management application built with a **6-
|
||||
- **Port**: 3001 (internal)
|
||||
- **Networks**: backend, database
|
||||
- **Dependencies**: PostgreSQL, Redis, Platform
|
||||
- **Health Check**: `http://localhost:3001/health` (30s interval)
|
||||
- **Health Check**: `https://motovaultpro.com/api/health` (30s interval)
|
||||
- **Configuration**:
|
||||
- `/app/config/production.yml` - Main config
|
||||
- `/app/config/shared.yml` - Shared config
|
||||
@@ -402,7 +402,7 @@ docker logs mvp-postgres -f
|
||||
curl https://motovaultpro.com/api/health # Backend (includes platform module)
|
||||
|
||||
# Or from within backend container
|
||||
docker compose exec mvp-backend curl http://localhost:3001/health
|
||||
docker compose exec mvp-backend curl https://motovaultpro.com/api/health
|
||||
```
|
||||
|
||||
### Database Access
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,295 +0,0 @@
|
||||
# Gas Stations Feature - Testing Implementation Report
|
||||
|
||||
## Overview
|
||||
Comprehensive test suite implemented for Phase 8 of the Gas Stations feature. All critical paths covered with unit, integration, and end-to-end tests.
|
||||
|
||||
## Test Files Created
|
||||
|
||||
### Backend Tests
|
||||
|
||||
#### Unit Tests (3 files)
|
||||
1. **`backend/src/features/stations/tests/unit/stations.service.test.ts`**
|
||||
- Test Coverage: StationsService business logic
|
||||
- Tests:
|
||||
- searchNearbyStations (happy path, sorting, metadata, default radius)
|
||||
- saveStation (success, not found, with metadata)
|
||||
- getUserSavedStations (returns all, empty array)
|
||||
- removeSavedStation (success, error handling, user isolation)
|
||||
- Total: 10 test cases
|
||||
|
||||
2. **`backend/src/features/stations/tests/unit/google-maps.client.test.ts`**
|
||||
- Test Coverage: Google Maps API client
|
||||
- Tests:
|
||||
- searchNearbyStations (success, API errors, zero results, network errors)
|
||||
- Distance calculation accuracy
|
||||
- Result formatting
|
||||
- Custom radius parameter
|
||||
- Caching behavior (multiple calls, different coordinates)
|
||||
- Total: 10 test cases
|
||||
|
||||
3. **`backend/src/features/stations/tests/fixtures/mock-stations.ts`** (existing, updated)
|
||||
- Mock data for all tests
|
||||
- Properly typed Station and SavedStation objects
|
||||
- Test coordinates for major cities
|
||||
|
||||
#### Integration Tests (1 file)
|
||||
4. **`backend/src/features/stations/tests/integration/stations.api.test.ts`**
|
||||
- Test Coverage: Complete API endpoint testing
|
||||
- Tests:
|
||||
- POST /api/stations/search (valid search, missing coordinates, auth required, coordinate validation)
|
||||
- POST /api/stations/save (save success, invalid placeId, station not in cache, user isolation)
|
||||
- GET /api/stations/saved (returns user stations, empty array, includes metadata)
|
||||
- DELETE /api/stations/saved/:placeId (delete success, 404 not found, ownership verification)
|
||||
- Error handling (Google Maps API errors, schema validation, authentication)
|
||||
- Total: 15 test cases
|
||||
|
||||
### Frontend Tests
|
||||
|
||||
#### Component Tests (1 file)
|
||||
5. **`frontend/src/features/stations/__tests__/components/StationCard.test.tsx`**
|
||||
- Test Coverage: StationCard component
|
||||
- Tests:
|
||||
- Rendering (name, address, photo, rating, distance)
|
||||
- Save/delete actions
|
||||
- Directions link (Google Maps integration)
|
||||
- Touch targets (44px minimum)
|
||||
- Card selection
|
||||
- Total: 10 test cases
|
||||
|
||||
#### Hook Tests (1 file)
|
||||
6. **`frontend/src/features/stations/__tests__/hooks/useStationsSearch.test.ts`**
|
||||
- Test Coverage: useStationsSearch React Query hook
|
||||
- Tests:
|
||||
- Search execution (basic, custom radius)
|
||||
- Loading states (pending, clearing after success)
|
||||
- Error handling (API errors, onError callback)
|
||||
- Success callback
|
||||
- Total: 6 test cases
|
||||
|
||||
#### API Client Tests (1 file)
|
||||
7. **`frontend/src/features/stations/__tests__/api/stations.api.test.ts`**
|
||||
- Test Coverage: Stations API client
|
||||
- Tests:
|
||||
- searchStations (valid request, without radius, error handling, 401/500 errors)
|
||||
- saveStation (with metadata, without optional fields, error handling)
|
||||
- getSavedStations (fetch all, empty array, error handling)
|
||||
- deleteSavedStation (delete success, 404 handling, error handling)
|
||||
- URL construction validation
|
||||
- Request payload validation
|
||||
- Response parsing
|
||||
- Total: 18 test cases
|
||||
|
||||
#### E2E Tests (1 file - Template)
|
||||
8. **`frontend/cypress/e2e/stations.cy.ts`**
|
||||
- Test Coverage: Complete user workflows
|
||||
- Tests:
|
||||
- Search for nearby stations (current location, manual coordinates, error handling, loading states)
|
||||
- View stations on map (markers, info windows, auto-fit)
|
||||
- Save station to favorites (save action, nickname/notes, prevent duplicates)
|
||||
- View saved stations list (display all, empty state, custom nicknames)
|
||||
- Delete saved station (delete action, optimistic removal, error handling)
|
||||
- Mobile navigation flow (tab switching, touch targets)
|
||||
- Error recovery (network errors, authentication errors)
|
||||
- Integration with fuel logs
|
||||
- Total: 20+ test scenarios
|
||||
|
||||
## Test Statistics
|
||||
|
||||
### Backend Tests
|
||||
- **Total Test Files**: 4 (3 unit + 1 integration)
|
||||
- **Total Test Cases**: ~35
|
||||
- **Coverage Target**: >80%
|
||||
- **Framework**: Jest with ts-jest
|
||||
- **Mocking**: jest.mock() for external dependencies
|
||||
|
||||
### Frontend Tests
|
||||
- **Total Test Files**: 3 (1 component + 1 hook + 1 API)
|
||||
- **Total Test Cases**: ~34
|
||||
- **E2E Template**: 1 file with 20+ scenarios
|
||||
- **Framework**: React Testing Library, Jest
|
||||
- **E2E Framework**: Cypress
|
||||
|
||||
### Combined
|
||||
- **Total Test Files**: 8
|
||||
- **Total Test Cases**: ~69 (excluding E2E)
|
||||
- **E2E Scenarios**: 20+
|
||||
- **Total Lines of Test Code**: ~2,500+
|
||||
|
||||
## Test Standards Applied
|
||||
|
||||
### Code Quality
|
||||
- Zero TypeScript errors
|
||||
- Zero lint warnings
|
||||
- Proper type safety with strict null checks
|
||||
- Clear test descriptions
|
||||
- Proper setup/teardown
|
||||
|
||||
### Testing Best Practices
|
||||
- Arrange-Act-Assert pattern
|
||||
- Mocking external dependencies (Google Maps API, database)
|
||||
- Test isolation (beforeEach cleanup)
|
||||
- Meaningful test names
|
||||
- Edge case coverage
|
||||
|
||||
### Coverage Areas
|
||||
1. **Happy Paths**: All successful user flows
|
||||
2. **Error Handling**: API failures, network errors, validation errors
|
||||
3. **Edge Cases**: Empty results, missing data, null values
|
||||
4. **User Isolation**: Data segregation by user_id
|
||||
5. **Authentication**: JWT requirements
|
||||
6. **Performance**: Response times, caching behavior
|
||||
7. **Mobile**: Touch targets, responsive design
|
||||
|
||||
## Key Fixes Applied
|
||||
|
||||
### TypeScript Strict Mode Compliance
|
||||
1. Fixed array access with optional chaining (`array[0]?.prop`)
|
||||
2. Fixed SavedStation type (uses `stationId` not `placeId`)
|
||||
3. Fixed Station optional properties (only set if defined)
|
||||
4. Fixed import paths (`buildApp` from `app.ts`)
|
||||
5. Removed unused imports
|
||||
|
||||
### Test Implementation Corrections
|
||||
1. Aligned test mocks with actual repository methods
|
||||
2. Corrected method names (`getUserSavedStations` vs `getSavedStations`)
|
||||
3. Fixed return type expectations
|
||||
4. Added proper error assertions
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Backend Tests
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# Run all stations tests
|
||||
npm test -- stations
|
||||
|
||||
# Run with coverage
|
||||
npm test -- stations --coverage
|
||||
|
||||
# Run specific test file
|
||||
npm test -- stations.service.test.ts
|
||||
```
|
||||
|
||||
### Frontend Tests
|
||||
```bash
|
||||
cd frontend
|
||||
|
||||
# Run all stations tests
|
||||
npm test -- stations
|
||||
|
||||
# Run with coverage
|
||||
npm test -- stations --coverage
|
||||
|
||||
# Run E2E tests
|
||||
npm run e2e
|
||||
# or
|
||||
npx cypress run --spec "cypress/e2e/stations.cy.ts"
|
||||
```
|
||||
|
||||
### Docker Container Tests
|
||||
```bash
|
||||
# Backend tests in container
|
||||
make shell-backend
|
||||
npm test -- stations
|
||||
|
||||
# Run from host
|
||||
docker compose exec mvp-backend npm test -- stations
|
||||
```
|
||||
|
||||
## Test Results (Expected)
|
||||
|
||||
### Unit Tests
|
||||
- All 10 service tests: PASS
|
||||
- All 10 Google Maps client tests: PASS
|
||||
- Mock data: Valid and type-safe
|
||||
|
||||
### Integration Tests
|
||||
- All 15 API endpoint tests: PASS (requires database)
|
||||
- User isolation verified
|
||||
- Error handling confirmed
|
||||
|
||||
### Frontend Tests
|
||||
- Component tests: PASS
|
||||
- Hook tests: PASS
|
||||
- API client tests: PASS
|
||||
|
||||
### E2E Tests
|
||||
- Template created for manual execution
|
||||
- Requires Google Maps API key
|
||||
- Requires Auth0 test user
|
||||
|
||||
## Coverage Report
|
||||
|
||||
Expected coverage after full test run:
|
||||
|
||||
```
|
||||
Feature: stations
|
||||
-------------------------------|---------|----------|---------|---------|
|
||||
File | % Stmts | % Branch | % Funcs | % Lines |
|
||||
-------------------------------|---------|----------|---------|---------|
|
||||
stations.service.ts | 85.7 | 80.0 | 100.0 | 85.7 |
|
||||
stations.repository.ts | 75.0 | 66.7 | 90.0 | 75.0 |
|
||||
google-maps.client.ts | 90.0 | 85.7 | 100.0 | 90.0 |
|
||||
stations.controller.ts | 80.0 | 75.0 | 100.0 | 80.0 |
|
||||
-------------------------------|---------|----------|---------|---------|
|
||||
All files | 82.5 | 77.2 | 97.5 | 82.5 |
|
||||
-------------------------------|---------|----------|---------|---------|
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Phase 9: Documentation
|
||||
- API documentation with examples
|
||||
- Setup instructions
|
||||
- Troubleshooting guide
|
||||
|
||||
### Phase 10: Validation & Polish
|
||||
- Run all tests in Docker
|
||||
- Fix any remaining linting issues
|
||||
- Manual testing (desktop + mobile)
|
||||
- Performance validation
|
||||
|
||||
### Phase 11: Deployment
|
||||
- Verify secrets configuration
|
||||
- Run migrations
|
||||
- Final smoke tests
|
||||
- Production checklist
|
||||
|
||||
## Notes
|
||||
|
||||
### Test Dependencies
|
||||
- Backend: Jest, Supertest, ts-jest, @types/jest
|
||||
- Frontend: @testing-library/react, @testing-library/jest-dom, @testing-library/user-event
|
||||
- E2E: Cypress (installed separately)
|
||||
|
||||
### Known Limitations
|
||||
1. Integration tests require database connection
|
||||
2. Google Maps API tests use mocks (not real API)
|
||||
3. E2E tests require manual execution (not in CI yet)
|
||||
4. Some tests may need Auth0 test credentials
|
||||
|
||||
### Future Improvements
|
||||
1. Add CI/CD pipeline integration
|
||||
2. Add snapshot testing for components
|
||||
3. Add performance benchmarks
|
||||
4. Add accessibility testing
|
||||
5. Add visual regression testing
|
||||
|
||||
## Conclusion
|
||||
|
||||
Comprehensive testing suite implemented covering:
|
||||
- 100% of backend service methods
|
||||
- 100% of API endpoints
|
||||
- All critical frontend components
|
||||
- All React Query hooks
|
||||
- All API client methods
|
||||
- Complete E2E user workflows
|
||||
|
||||
All tests follow MotoVaultPro testing standards and are ready for integration into the continuous integration pipeline.
|
||||
|
||||
---
|
||||
|
||||
**Testing Complete**: Phase 8 ✅
|
||||
**Report Generated**: 2025-11-04
|
||||
**Author**: Feature Capsule Agent
|
||||
@@ -1,648 +0,0 @@
|
||||
Gas Stations Feature - Complete Implementation Plan (K8s-Aligned Secrets)
|
||||
|
||||
Overview
|
||||
|
||||
Implement complete Gas Stations feature with full integration, map visualization, backend improvements, and comprehensive testing. Uses K8s-aligned runtime
|
||||
secrets pattern for frontend Google Maps API key.
|
||||
|
||||
Phase 1: Frontend Secrets Infrastructure (K8s-Aligned)
|
||||
|
||||
1.1 Create Frontend Config Loader
|
||||
|
||||
Create frontend/scripts/load-config.sh:
|
||||
- Bash script that runs as container entrypoint
|
||||
- Reads /run/secrets/google-maps-api-key
|
||||
- Generates /usr/share/nginx/html/config.js with: window.CONFIG = { googleMapsApiKey: '...' }
|
||||
- Falls back to empty string if secret not found
|
||||
- Logs success/failure for debugging
|
||||
|
||||
1.2 Update Frontend Dockerfile
|
||||
|
||||
Modify frontend/Dockerfile:
|
||||
- Copy load-config.sh into image
|
||||
- Make script executable
|
||||
- Add SECRETS_DIR environment variable (default: /run/secrets)
|
||||
- Update CMD to run script before nginx: ["sh", "-c", "/app/load-config.sh && nginx -g 'daemon off;'"]
|
||||
|
||||
1.3 Update Frontend index.html
|
||||
|
||||
Modify frontend/index.html:
|
||||
- Add <script src="/config.js"></script> before app bundle
|
||||
- Config loads before React app initializes
|
||||
- Add TypeScript types for window.CONFIG
|
||||
|
||||
1.4 Create Frontend Config Types
|
||||
|
||||
Create frontend/src/core/config/config.types.ts:
|
||||
- Interface for window.CONFIG
|
||||
- Export typed accessor: getConfig()
|
||||
- Runtime validation (throw if required values missing)
|
||||
|
||||
1.5 Update docker-compose.yml
|
||||
|
||||
Add to mvp-frontend service:
|
||||
volumes:
|
||||
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
|
||||
environment:
|
||||
SECRETS_DIR: /run/secrets
|
||||
|
||||
1.6 Document Pattern
|
||||
|
||||
Create frontend/docs/RUNTIME-CONFIG.md:
|
||||
- Explain runtime config pattern
|
||||
- How to add new runtime secrets
|
||||
- K8s deployment notes
|
||||
- Local development setup
|
||||
|
||||
Phase 2: Backend Improvements
|
||||
|
||||
2.1 Add Circuit Breaker Pattern
|
||||
|
||||
- Install opossum package: backend/package.json
|
||||
- Create backend/src/features/stations/external/google-maps/google-maps.circuit-breaker.ts
|
||||
- Update google-maps.client.ts to wrap API calls
|
||||
- Follow platform feature pattern (reference: backend/src/features/platform/)
|
||||
- Config: 10s timeout, 50% threshold, 30s reset
|
||||
|
||||
2.2 Backend Unit Tests
|
||||
|
||||
Create backend/src/features/stations/tests/unit/:
|
||||
- stations.service.test.ts - Business logic, user isolation
|
||||
- stations.repository.test.ts - Database operations, SQL queries
|
||||
- google-maps.client.test.ts - API mocking, distance calculation
|
||||
- Use test fixtures for sample data
|
||||
|
||||
2.3 Backend Integration Tests
|
||||
|
||||
Create backend/src/features/stations/tests/integration/:
|
||||
- stations.api.test.ts - Full endpoint testing with real database
|
||||
- saved-stations.flow.test.ts - Save, retrieve, delete workflow
|
||||
- Use test database (configured in test setup)
|
||||
|
||||
2.4 Test Fixtures
|
||||
|
||||
Create backend/src/features/stations/tests/fixtures/:
|
||||
- mock-stations.ts - Sample station objects
|
||||
- mock-google-response.ts - Mock Google Places API responses
|
||||
- test-coordinates.ts - Test location data
|
||||
|
||||
2.5 Cache Cleanup Job
|
||||
|
||||
Create backend/src/features/stations/jobs/cache-cleanup.job.ts:
|
||||
- Scheduled job (daily at 2 AM)
|
||||
- Delete station_cache entries older than 24 hours
|
||||
- Log cleanup metrics (rows deleted)
|
||||
- Register in main app scheduler
|
||||
|
||||
Phase 3: Frontend Foundation
|
||||
|
||||
3.1 Type Definitions
|
||||
|
||||
Create frontend/src/features/stations/types/stations.types.ts:
|
||||
- Station interface (id, name, address, location, rating, distance, photoUrl, prices)
|
||||
- SavedStation interface (extends Station, adds nickname, notes, isFavorite)
|
||||
- SearchRequest interface (latitude, longitude, radius?, fuelType?)
|
||||
- SearchResponse interface
|
||||
- MapMarker interface for map pins
|
||||
|
||||
3.2 API Client
|
||||
|
||||
Create frontend/src/features/stations/api/stations.api.ts:
|
||||
- searchStations(request: SearchRequest): Promise<Station[]>
|
||||
- saveStation(placeId: string, data: SaveStationData): Promise<SavedStation>
|
||||
- getSavedStations(): Promise<SavedStation[]>
|
||||
- deleteSavedStation(placeId: string): Promise<void>
|
||||
- Use axios with proper error handling
|
||||
- Add request/response logging
|
||||
|
||||
3.3 React Query Hooks
|
||||
|
||||
Create frontend/src/features/stations/hooks/:
|
||||
|
||||
useStationsSearch.ts:
|
||||
- Mutation hook for search (not cached by default)
|
||||
- Accepts SearchRequest
|
||||
- Returns Station array
|
||||
- Integrates with useGeolocation
|
||||
|
||||
useSavedStations.ts:
|
||||
- Query hook with React Query caching
|
||||
- Auto-refetch on window focus
|
||||
- Invalidate on save/delete mutations
|
||||
|
||||
useSaveStation.ts:
|
||||
- Mutation hook
|
||||
- Optimistic updates to saved list
|
||||
- Invalidates saved stations cache on success
|
||||
|
||||
useDeleteStation.ts:
|
||||
- Mutation hook
|
||||
- Optimistic removal from list
|
||||
- Invalidates cache on success
|
||||
|
||||
useGeolocation.ts:
|
||||
- Browser Geolocation API wrapper
|
||||
- Permission handling
|
||||
- Error states (permission denied, unavailable, timeout)
|
||||
- Returns current position
|
||||
|
||||
3.4 Google Maps Integration
|
||||
|
||||
Create frontend/src/features/stations/utils/maps-loader.ts:
|
||||
- Load Google Maps JavaScript API dynamically
|
||||
- Use runtime config: getConfig().googleMapsApiKey
|
||||
- Promise-based API
|
||||
- Singleton pattern (load once)
|
||||
- TypeScript types for Google Maps objects
|
||||
|
||||
Phase 4: Frontend Components
|
||||
|
||||
4.1 Core Components
|
||||
|
||||
Create frontend/src/features/stations/components/:
|
||||
|
||||
StationCard.tsx:
|
||||
- Material-UI Card component
|
||||
- Props: station, onSave, onDelete, isSaved
|
||||
- Display: name (Typography h6), address (Typography body2), distance (Chip)
|
||||
- Rating stars (Rating component)
|
||||
- Price display (if available)
|
||||
- Photo thumbnail (if available)
|
||||
- Save/Unsave IconButton (BookmarkIcon / BookmarkBorderIcon)
|
||||
- Directions link (opens Google Maps)
|
||||
- Mobile: 44px min height, touch targets
|
||||
- Desktop: hover effects
|
||||
|
||||
StationsList.tsx:
|
||||
- Container for search results
|
||||
- Props: stations[], loading, error, onSaveStation
|
||||
- Grid layout (responsive: 1 col mobile, 2 cols tablet, 3 cols desktop)
|
||||
- Loading skeleton (Skeleton components)
|
||||
- Empty state: "No stations found. Try adjusting your search."
|
||||
- Error state with retry button
|
||||
|
||||
SavedStationsList.tsx:
|
||||
- User's favorites display
|
||||
- Props: savedStations[], onDelete, onSelect
|
||||
- List layout (vertical)
|
||||
- Show nickname if set, otherwise station name
|
||||
- Notes preview (truncated)
|
||||
- Distance from current location (optional)
|
||||
- Delete IconButton
|
||||
- Empty state: "No saved stations yet. Save stations from search results."
|
||||
|
||||
StationsSearchForm.tsx:
|
||||
- Form with search parameters
|
||||
- Current Location button (uses useGeolocation)
|
||||
- Manual lat/lng inputs (number fields)
|
||||
- Radius slider (FormControl, Slider: 1-25 miles, default 5)
|
||||
- Fuel type select (optional filter)
|
||||
- Search button (LoadingButton)
|
||||
- Loading states on button
|
||||
- Validation: require location (current OR manual)
|
||||
- Error display for geolocation failures
|
||||
|
||||
StationMap.tsx:
|
||||
- Google Maps embed component
|
||||
- Props: stations[], center, zoom, onMarkerClick
|
||||
- Load map using maps-loader utility
|
||||
- Station markers (color-coded: blue=normal, gold=saved)
|
||||
- Current location marker (red pin)
|
||||
- Info windows on marker click (station details + save button)
|
||||
- Directions button in info window
|
||||
- Zoom controls, pan controls
|
||||
- Responsive height (300px mobile, 500px desktop)
|
||||
|
||||
4.2 UI Utilities
|
||||
|
||||
Create frontend/src/features/stations/utils/:
|
||||
|
||||
distance.ts:
|
||||
- formatDistance(meters: number): string - "1.2 mi" or "0.3 mi"
|
||||
- calculateDistance(lat1, lng1, lat2, lng2): number - Haversine formula
|
||||
|
||||
location.ts:
|
||||
- getCurrentPosition(): Promise<GeolocationPosition>
|
||||
- requestLocationPermission(): Promise<boolean>
|
||||
- Error handling helpers
|
||||
|
||||
map-utils.ts:
|
||||
- createStationMarker(station, map, isSaved): google.maps.Marker
|
||||
- createInfoWindow(station): google.maps.InfoWindow
|
||||
- fitBoundsToMarkers(map, markers): void
|
||||
|
||||
Phase 5: Desktop Implementation
|
||||
|
||||
5.1 Desktop Page
|
||||
|
||||
Create frontend/src/features/stations/pages/StationsPage.tsx:
|
||||
- Layout: Grid container
|
||||
- Left column (60%): StationMap (full height)
|
||||
- Right column (40%):
|
||||
- StationsSearchForm at top
|
||||
- Tabs: "Search Results" | "Saved Stations"
|
||||
- Tab 1: StationsList
|
||||
- Tab 2: SavedStationsList
|
||||
- State management:
|
||||
- searchResults (from search mutation)
|
||||
- savedStations (from query)
|
||||
- selectedStation (for map focus)
|
||||
- Effects:
|
||||
- Load saved stations on mount
|
||||
- Update map when search results change
|
||||
- Mobile breakpoint: Stack vertically (map on top)
|
||||
|
||||
5.2 Desktop Routing
|
||||
|
||||
Update frontend/src/App.tsx (line 556):
|
||||
- Remove: <Route path="/stations" element={<div>Stations (TODO)</div>} />
|
||||
- Add: <Route path="/stations" element={<StationsPage />} />
|
||||
- Import: import { StationsPage } from './features/stations/pages/StationsPage'
|
||||
- Ensure route is inside ProtectedRoute wrapper
|
||||
|
||||
Phase 6: Mobile Implementation
|
||||
|
||||
6.1 Mobile Screen
|
||||
|
||||
Create frontend/src/features/stations/pages/StationsMobileScreen.tsx:
|
||||
- BottomNavigation with 3 tabs: Search, Saved, Map
|
||||
- Tab 0 (Search):
|
||||
- StationsSearchForm (compact mode)
|
||||
- StationsList (vertical scroll)
|
||||
- Pull-to-refresh support
|
||||
- Tab 1 (Saved):
|
||||
- SavedStationsList (full screen)
|
||||
- Swipe to delete gestures
|
||||
- Tab 2 (Map):
|
||||
- StationMap (full screen, 100vh)
|
||||
- Floating search button (FAB) to go back to search tab
|
||||
- Bottom sheet for station details (SwipeableDrawer)
|
||||
- Touch targets: 44px minimum
|
||||
- Safe area insets for notched devices
|
||||
|
||||
6.2 Mobile Routing
|
||||
|
||||
Update frontend/src/App.tsx:
|
||||
- Add mobile route: <Route path="/m/stations" element={<StationsMobileScreen />} />
|
||||
- Update mobile navigation items (add stations)
|
||||
- Ensure icon is interactive (onClick navigation)
|
||||
|
||||
Phase 7: Fuel Logs Integration
|
||||
|
||||
7.1 Station Picker Component
|
||||
|
||||
Create frontend/src/features/fuel-logs/components/StationPicker.tsx:
|
||||
- Autocomplete component (Material-UI)
|
||||
- Props: value, onChange, userLocation?
|
||||
- Options:
|
||||
- Saved stations (shown first, grouped)
|
||||
- Nearby stations (if location available)
|
||||
- Manual text input (freeSolo mode)
|
||||
- Option rendering:
|
||||
- Station name (primary)
|
||||
- Distance + address (secondary)
|
||||
- Bookmark icon if saved
|
||||
- Debounced search (300ms)
|
||||
- Loading indicator while searching
|
||||
- Fallback to text input if API fails
|
||||
|
||||
7.2 Update Fuel Log Form
|
||||
|
||||
Modify frontend/src/features/fuel-logs/components/FuelLogForm.tsx:
|
||||
- Replace LocationInput with StationPicker
|
||||
- Props: pass user location from geolocation
|
||||
- Value binding to stationName field
|
||||
- Maintain backward compatibility (accepts text string)
|
||||
- Show nearby stations on field focus
|
||||
- Optional: "Save this station" checkbox when new station entered
|
||||
|
||||
7.3 Integration Features
|
||||
|
||||
Optional enhancements:
|
||||
- In FuelLogCard, link station name to station details
|
||||
- In StationsPage, show "Recent fuel logs at this station"
|
||||
- Suggest saving station after logging fuel at new location
|
||||
|
||||
Phase 8: Testing
|
||||
|
||||
8.1 Backend Tests Execution
|
||||
|
||||
cd backend
|
||||
npm test -- features/stations
|
||||
- Verify all unit tests pass
|
||||
- Verify all integration tests pass
|
||||
- Check coverage report (aim for >80%)
|
||||
- Fix any failing tests
|
||||
|
||||
8.2 Frontend Component Tests
|
||||
|
||||
Create frontend/src/features/stations/__tests__/components/:
|
||||
- StationCard.test.tsx - Rendering, save/delete actions
|
||||
- StationsList.test.tsx - List rendering, empty/error states
|
||||
- SavedStationsList.test.tsx - Saved list, delete action
|
||||
- StationsSearchForm.test.tsx - Form validation, submission
|
||||
- StationMap.test.tsx - Map initialization (with mocks)
|
||||
- Use React Testing Library
|
||||
- Mock API calls with MSW (Mock Service Worker)
|
||||
|
||||
8.3 Frontend Page Tests
|
||||
|
||||
Create frontend/src/features/stations/__tests__/pages/:
|
||||
- StationsPage.test.tsx - Desktop page integration
|
||||
- StationsMobileScreen.test.tsx - Mobile navigation, tabs
|
||||
- Test user workflows: search, save, delete
|
||||
- Test error handling and loading states
|
||||
|
||||
8.4 API Client Tests
|
||||
|
||||
Create frontend/src/features/stations/api/__tests__/:
|
||||
- stations.api.test.ts - Test all API methods
|
||||
- Mock axios responses
|
||||
- Test error handling (network, 401, 500)
|
||||
- Verify request payloads
|
||||
|
||||
8.5 React Query Hook Tests
|
||||
|
||||
Create frontend/src/features/stations/hooks/__tests__/:
|
||||
- Test each hook in isolation
|
||||
- Mock React Query
|
||||
- Test loading, success, error states
|
||||
- Test cache invalidation logic
|
||||
- Test optimistic updates
|
||||
|
||||
8.6 E2E Tests
|
||||
|
||||
Create frontend/cypress/e2e/stations.cy.ts (or similar E2E framework):
|
||||
- Test: Search for nearby stations
|
||||
- Test: Save a station to favorites
|
||||
- Test: View saved stations list
|
||||
- Test: Delete a saved station
|
||||
- Test: Use station in fuel log entry
|
||||
- Test: Mobile navigation flow
|
||||
- Test: Map marker click and info window
|
||||
- Use real backend API in test environment
|
||||
|
||||
Phase 9: Documentation
|
||||
|
||||
9.1 Backend Feature Docs
|
||||
|
||||
Create backend/src/features/stations/docs/:
|
||||
|
||||
ARCHITECTURE.md:
|
||||
- System design overview
|
||||
- Data flow diagrams
|
||||
- External dependencies (Google Maps API)
|
||||
- Caching strategy
|
||||
- Database schema
|
||||
- Circuit breaker pattern
|
||||
|
||||
API.md:
|
||||
- All endpoint documentation
|
||||
- Request/response examples (curl commands)
|
||||
- Authentication requirements
|
||||
- Rate limits and quotas
|
||||
- Error responses
|
||||
|
||||
TESTING.md:
|
||||
- How to run tests
|
||||
- Test database setup
|
||||
- Writing new tests
|
||||
- Coverage goals
|
||||
|
||||
GOOGLE-MAPS-SETUP.md:
|
||||
- API key creation in Google Cloud Console
|
||||
- Required APIs to enable
|
||||
- Quota management
|
||||
- Cost estimation
|
||||
- Security best practices
|
||||
|
||||
9.2 Frontend Feature Docs
|
||||
|
||||
Create frontend/src/features/stations/README.md:
|
||||
- Feature overview
|
||||
- Component hierarchy
|
||||
- Hook usage examples
|
||||
- Adding new functionality
|
||||
- Runtime config pattern
|
||||
- Testing guide
|
||||
|
||||
9.3 Runtime Config Documentation
|
||||
|
||||
Already created in Phase 1.6: frontend/docs/RUNTIME-CONFIG.md
|
||||
|
||||
9.4 Update Main Documentation
|
||||
|
||||
Update docs/README.md:
|
||||
- Add "Gas Stations" to features list
|
||||
- Link to backend and frontend docs
|
||||
- Mention Google Maps integration
|
||||
|
||||
Update backend/src/features/stations/README.md:
|
||||
- Complete feature overview
|
||||
- Configuration requirements
|
||||
- API endpoints summary
|
||||
- Testing instructions
|
||||
- Deployment notes
|
||||
|
||||
Phase 10: Validation & Polish
|
||||
|
||||
10.1 Docker Build & Test
|
||||
|
||||
make rebuild # Rebuild all containers
|
||||
make logs # Watch for errors
|
||||
make migrate # Run database migrations
|
||||
make test # Run all tests
|
||||
|
||||
Verify:
|
||||
- Frontend config.js is generated at startup
|
||||
- Backend loads Google Maps API key from secret
|
||||
- All containers start without errors
|
||||
- Database migrations create tables
|
||||
- Tests pass in container environment
|
||||
|
||||
10.2 Linting & Formatting
|
||||
|
||||
# Backend
|
||||
cd backend && npm run lint && npm run type-check
|
||||
|
||||
# Frontend
|
||||
cd frontend && npm run lint && npm run type-check
|
||||
|
||||
Fix all issues (MUST be 100% green per CLAUDE.md):
|
||||
- Zero TypeScript errors
|
||||
- Zero ESLint warnings
|
||||
- Zero Prettier formatting issues
|
||||
|
||||
10.3 Manual Testing - Desktop
|
||||
|
||||
- Open https://motovaultpro.com/stations in browser
|
||||
- Test geolocation permission flow
|
||||
- Search for stations near current location
|
||||
- Verify map loads with markers
|
||||
- Click marker, verify info window
|
||||
- Save a station
|
||||
- View saved stations list
|
||||
- Delete a saved station
|
||||
- Test manual lat/lng search
|
||||
- Test with no results (remote location)
|
||||
- Test error handling (API failure)
|
||||
|
||||
10.4 Manual Testing - Mobile
|
||||
|
||||
- Open mobile nav, navigate to Stations
|
||||
- Test all 3 tabs (Search, Saved, Map)
|
||||
- Verify 44px touch targets
|
||||
- Test swipe gestures
|
||||
- Test bottom sheet interactions
|
||||
- Verify responsive layout
|
||||
- Test on actual mobile device (if possible)
|
||||
|
||||
10.5 Fuel Logs Integration Testing
|
||||
|
||||
- Navigate to Fuel Logs
|
||||
- Create new fuel log entry
|
||||
- Test StationPicker autocomplete
|
||||
- Select a saved station
|
||||
- Enter new station name
|
||||
- Submit fuel log
|
||||
- Verify station name is saved
|
||||
|
||||
10.6 Cross-Browser Testing
|
||||
|
||||
Test on:
|
||||
- Chrome (latest)
|
||||
- Safari (latest)
|
||||
- Firefox (latest)
|
||||
- Mobile Safari (iOS)
|
||||
- Mobile Chrome (Android)
|
||||
|
||||
10.7 Performance Validation
|
||||
|
||||
- Check map load time (<2s)
|
||||
- Verify API response times (<500ms)
|
||||
- Check Redis cache hits (backend logs)
|
||||
- Monitor Google Maps API quota usage
|
||||
- Verify no memory leaks (React DevTools Profiler)
|
||||
|
||||
Phase 11: Deployment Preparation
|
||||
|
||||
11.1 Configuration Checklist
|
||||
|
||||
Backend:
|
||||
- Secret file exists: ./secrets/app/google-maps-api-key.txt
|
||||
- Docker compose mounts secret to /run/secrets/google-maps-api-key
|
||||
- Backend config loader reads from SECRETS_DIR
|
||||
|
||||
Frontend:
|
||||
- Secret file mounted: /run/secrets/google-maps-api-key
|
||||
- Entrypoint script generates /usr/share/nginx/html/config.js
|
||||
- index.html loads config.js before app
|
||||
- App accesses via getConfig().googleMapsApiKey
|
||||
|
||||
11.2 Database Migration
|
||||
|
||||
make migrate
|
||||
Verify:
|
||||
- Migration 001_create_stations_tables.sql runs successfully
|
||||
- Tables created: station_cache, saved_stations
|
||||
- Indexes created on both tables
|
||||
- Test queries work
|
||||
|
||||
11.3 Secrets Verification
|
||||
|
||||
# Verify backend can read secret
|
||||
docker compose exec mvp-backend cat /run/secrets/google-maps-api-key
|
||||
|
||||
# Verify frontend generates config
|
||||
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
|
||||
# Should show: window.CONFIG = { googleMapsApiKey: "..." }
|
||||
|
||||
11.4 Health Checks
|
||||
|
||||
- Verify /health endpoint includes stations feature
|
||||
- Test stations API endpoints with curl:
|
||||
# Get JWT token first
|
||||
TOKEN="your-jwt-token"
|
||||
|
||||
# Search stations
|
||||
curl -X POST http://localhost:3001/api/stations/search \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"latitude": 37.7749, "longitude": -122.4194}'
|
||||
|
||||
# Get saved stations
|
||||
curl http://localhost:3001/api/stations/saved \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
11.5 Production Readiness
|
||||
|
||||
Update config/app/production.yml.example:
|
||||
- Document Google Maps API key requirement
|
||||
- Add example configuration
|
||||
|
||||
Create deployment checklist:
|
||||
- Google Maps API key in secrets
|
||||
- Frontend container mounts secret
|
||||
- Backend container mounts secret
|
||||
- Database migrations run
|
||||
- All tests pass
|
||||
- Linters pass
|
||||
- Manual testing complete
|
||||
- Performance acceptable
|
||||
- Documentation complete
|
||||
|
||||
Success Criteria (per CLAUDE.md)
|
||||
|
||||
- All linters pass with zero issues
|
||||
- All tests pass (backend + frontend)
|
||||
- Feature works end-to-end on desktop
|
||||
- Feature works end-to-end on mobile
|
||||
- Fuel logs integration functional
|
||||
- Map visualization working
|
||||
- Google Maps API integration live via runtime secrets
|
||||
- K8s-aligned secrets pattern implemented
|
||||
- Documentation complete
|
||||
- Old code deleted (no orphaned files)
|
||||
|
||||
K8s Secrets Pattern Summary
|
||||
|
||||
Backend (already implemented):
|
||||
- Secret mounted: /run/secrets/google-maps-api-key
|
||||
- Read at runtime by config-loader.ts
|
||||
- Never in environment variables or code
|
||||
|
||||
Frontend (new implementation):
|
||||
- Secret mounted: /run/secrets/google-maps-api-key
|
||||
- Read at container startup by entrypoint script
|
||||
- Injected into config.js served with static files
|
||||
- App loads config at runtime via window.CONFIG
|
||||
- Can be updated by restarting container (no rebuild)
|
||||
|
||||
This mirrors K8s deployment where:
|
||||
- Secrets are mounted as volumes
|
||||
- Applications read them at runtime
|
||||
- Secrets can be rotated without rebuilding images
|
||||
|
||||
Estimated Timeline
|
||||
|
||||
- Phase 1 (Frontend Secrets): 1 day
|
||||
- Phase 2 (Backend): 1 day
|
||||
- Phase 3 (Foundation): 0.5 day
|
||||
- Phase 4 (Components): 1 day
|
||||
- Phase 5 (Desktop): 0.5 day
|
||||
- Phase 6 (Mobile): 0.5 day
|
||||
- Phase 7 (Integration): 0.5 day
|
||||
- Phase 8 (Testing): 1.5 days
|
||||
- Phase 9 (Docs): 0.5 day
|
||||
- Phase 10 (Validation): 0.5 day
|
||||
- Phase 11 (Deployment): 0.5 day
|
||||
|
||||
Total: 8 days (full implementation with K8s-aligned secrets)
|
||||
|
||||
Reference Implementations
|
||||
|
||||
- Secrets pattern: Backend config-loader.ts and docker-compose.yml
|
||||
- Feature structure: frontend/src/features/vehicles/
|
||||
- Component patterns: Vehicles components
|
||||
- Testing: Vehicles test suite
|
||||
- Mobile + Desktop: Vehicles pages
|
||||
File diff suppressed because it is too large
Load Diff
259
docs/PROMPTS.md
259
docs/PROMPTS.md
@@ -1,260 +1 @@
|
||||
*** CRITICAL ***: Never read this file. Stop and move on.
|
||||
|
||||
*** STARTING PROMPT ***
|
||||
|
||||
## Agent Team (Specialized Development)
|
||||
|
||||
MotoVaultPro uses a 4-agent team for optimal development velocity and quality enforcement.
|
||||
|
||||
### When to Use Agents
|
||||
|
||||
**Feature Capsule Agent** - Backend feature development
|
||||
- Implementing new features in `backend/src/features/{feature}/`
|
||||
- Creating API endpoints, business logic, database access
|
||||
- Platform service integration
|
||||
- Backend tests and migrations
|
||||
|
||||
**Platform Feature Agent** - Integrated platform module
|
||||
- Enhancing `backend/src/features/platform/`
|
||||
- Vehicle lookup + VIN decoding logic
|
||||
- Redis/SQL performance tuning for platform endpoints
|
||||
- Platform module tests and documentation
|
||||
|
||||
**Mobile-First Frontend Agent** - Responsive UI/UX
|
||||
- React components in `frontend/src/features/{feature}/`
|
||||
- Mobile + desktop responsive design (NON-NEGOTIABLE)
|
||||
- Forms, validation, and React Query integration
|
||||
- Frontend tests and accessibility
|
||||
|
||||
**Quality Enforcer Agent** - Quality assurance
|
||||
- Running complete test suites
|
||||
- Validating linting and type checking
|
||||
- Enforcing "all green" policy (ZERO TOLERANCE)
|
||||
- Mobile + desktop validation
|
||||
|
||||
### Agent Spawning Examples
|
||||
|
||||
```
|
||||
# Backend feature development
|
||||
Task: "Implement {feature} backend following feature capsule pattern.
|
||||
Read backend/src/features/{feature}/README.md and implement API, domain, data layers with tests."
|
||||
Agent: Feature Capsule Agent
|
||||
|
||||
# Frontend development
|
||||
Task: "Build responsive UI for {feature}. Read backend API docs and implement mobile-first.
|
||||
Test on 320px and 1920px viewports."
|
||||
Agent: Mobile-First Frontend Agent
|
||||
|
||||
# Platform module work
|
||||
Task: "Enhance platform feature capsule (backend/src/features/platform).
|
||||
Implement API/domain/data changes with accompanying tests."
|
||||
Agent: Platform Feature Agent
|
||||
|
||||
# Quality validation
|
||||
Task: "Validate {feature} quality gates. Run all tests, check linting, verify mobile + desktop.
|
||||
Report pass/fail with details."
|
||||
Agent: Quality Enforcer Agent
|
||||
```
|
||||
|
||||
### Agent Coordination Workflow
|
||||
|
||||
1. Feature Capsule Agent → Implements backend
|
||||
2. Mobile-First Frontend Agent → Implements UI (parallel)
|
||||
3. Quality Enforcer Agent → Validates everything
|
||||
4. Expert Software Architect → Reviews and approves
|
||||
|
||||
### When Coordinator Handles Directly
|
||||
|
||||
- Quick bug fixes (single file)
|
||||
- Documentation updates
|
||||
- Configuration changes
|
||||
- Simple code reviews
|
||||
- Answering questions
|
||||
|
||||
## Key Commands
|
||||
|
||||
- Start: `make start`
|
||||
- Rebuild: `make rebuild`
|
||||
- Logs: `make logs`
|
||||
- Test: `make test`
|
||||
- Migrate: `make migrate`
|
||||
- Shell (backend): `make shell-backend`
|
||||
- Shell (frontend): `make shell-frontend`
|
||||
|
||||
## Development Rules
|
||||
|
||||
1. NEVER use emojis in code or documentation
|
||||
2. Every feature MUST be responsive (mobile + desktop) - NON-NEGOTIABLE
|
||||
3. Testing and debugging can be done locally
|
||||
4. All testing and debugging needs to be verified in containers
|
||||
5. Each backend feature is self-contained in `backend/src/features/{name}/`
|
||||
6. Delete old code when replacing (no commented code)
|
||||
7. Use meaningful variable names (`userID` not `id`)
|
||||
8. ALL quality gates must pass (all green policy)
|
||||
9. Feature capsules are self-contained modules
|
||||
|
||||
## Making Changes
|
||||
|
||||
### Frontend Changes (React)
|
||||
**Agent**: Mobile-First Frontend Agent
|
||||
- Components: `frontend/src/features/{feature}/components/`
|
||||
- Types: `frontend/src/features/{feature}/types/`
|
||||
- After changes: `make rebuild` then test at https://admin.motovaultpro.com
|
||||
- MUST test on mobile (320px) AND desktop (1920px)
|
||||
|
||||
### Backend Changes (Node.js)
|
||||
**Agent**: Feature Capsule Agent
|
||||
- API: `backend/src/features/{feature}/api/`
|
||||
- Business logic: `backend/src/features/{feature}/domain/`
|
||||
- Database: `backend/src/features/{feature}/data/`
|
||||
- After changes: `make rebuild` then check logs
|
||||
|
||||
### Platform Module Changes (TypeScript)
|
||||
**Agent**: Platform Feature Agent
|
||||
- Feature capsule: `backend/src/features/platform/`
|
||||
- API routes: `backend/src/features/platform/api/`
|
||||
- Domain/data: `backend/src/features/platform/domain/` and `data/`
|
||||
- After changes: `make rebuild` then verify platform endpoints via backend logs/tests
|
||||
|
||||
### Database Changes
|
||||
- Add migration: `backend/src/features/{feature}/migrations/00X_description.sql`
|
||||
- Run: `make migrate`
|
||||
- Validate: Check logs and test affected features
|
||||
|
||||
### Adding NPM Packages
|
||||
- Edit `package.json` (frontend or backend)
|
||||
- Run `make rebuild` (no local npm install)
|
||||
- Containers handle dependency installation
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Add a New Feature (Full Stack)
|
||||
1. Spawn Feature Capsule Agent for backend
|
||||
2. Spawn Mobile-First Frontend Agent for UI (parallel)
|
||||
3. Feature Capsule Agent: API + domain + data + tests
|
||||
4. Mobile-First Agent: Components + forms + tests
|
||||
5. Spawn Quality Enforcer Agent for validation
|
||||
6. Review and approve
|
||||
|
||||
### Add a Form Field
|
||||
1. Update types in frontend/backend
|
||||
2. Add to database migration if needed
|
||||
3. Update React form component (Mobile-First Agent)
|
||||
4. Update backend validation (Feature Capsule Agent)
|
||||
5. Test with `make rebuild`
|
||||
6. Validate with Quality Enforcer Agent
|
||||
|
||||
### Add New API Endpoint
|
||||
**Agent**: Feature Capsule Agent
|
||||
1. Create route in `backend/src/features/{feature}/api/`
|
||||
2. Add service method in `domain/`
|
||||
3. Add repository method in `data/`
|
||||
4. Write unit and integration tests
|
||||
5. Test with `make rebuild`
|
||||
|
||||
### Fix UI Responsiveness
|
||||
**Agent**: Mobile-First Frontend Agent
|
||||
1. Use Tailwind classes: `sm:`, `md:`, `lg:`
|
||||
2. Test on mobile viewport (320px, 375px, 768px)
|
||||
3. Test on desktop viewport (1024px, 1920px)
|
||||
4. Ensure touch targets are 44px minimum
|
||||
5. Validate keyboard navigation on desktop
|
||||
|
||||
### Add Platform Integration
|
||||
**Agents**: Platform Feature Agent + Feature Capsule Agent
|
||||
1. Platform Feature Agent: Implement/update platform endpoint logic
|
||||
2. Feature Capsule Agent: Update consuming feature client (e.g. `external/platform-vehicles/`)
|
||||
3. Feature Capsule Agent: Adjust caching/circuit breaker strategies as needed
|
||||
4. Joint testing: run targeted unit/integration suites
|
||||
5. Quality Enforcer Agent: Validate end-to-end
|
||||
|
||||
### Run Quality Checks
|
||||
**Agent**: Quality Enforcer Agent
|
||||
1. Run all tests: `make test`
|
||||
2. Check linting: `npm run lint` (backend container)
|
||||
3. Check types: `npm run type-check` (backend container)
|
||||
4. Validate mobile + desktop
|
||||
5. Report pass/fail with details
|
||||
|
||||
## Quality Gates (MANDATORY)
|
||||
|
||||
Code is complete when:
|
||||
- All linters pass with zero issues
|
||||
- All tests pass (100% green)
|
||||
- Feature works end-to-end
|
||||
- Mobile + desktop validated (for frontend)
|
||||
- Old code is deleted
|
||||
- Documentation updated
|
||||
- Test coverage >= 80% for new code
|
||||
|
||||
## Architecture Quick Reference
|
||||
|
||||
### Application Stack
|
||||
- **Backend Feature Capsules**: Modular monolith in `backend/src/features/`
|
||||
- **Platform Module**: Vehicle data + VIN decoding in `backend/src/features/platform/`
|
||||
- **Frontend**: React SPA in `frontend/src/`
|
||||
|
||||
### Feature Capsule Pattern
|
||||
Each feature is self-contained:
|
||||
```
|
||||
backend/src/features/{feature}/
|
||||
├── README.md # Complete feature documentation
|
||||
├── api/ # HTTP layer
|
||||
├── domain/ # Business logic
|
||||
├── data/ # Database access
|
||||
├── migrations/ # Schema changes
|
||||
├── external/ # Platform service clients
|
||||
└── tests/ # Unit + integration tests
|
||||
```
|
||||
|
||||
|
||||
## Important Context
|
||||
|
||||
- **Auth**: Frontend uses Auth0, backend validates JWTs
|
||||
- **Database**: PostgreSQL with user-isolated data (user_id scoping)
|
||||
- **Platform APIs**: Exposed via `/api/platform/*`, secured with Auth0 JWTs
|
||||
- **Caching**: Redis with feature-specific TTL strategies
|
||||
- **Testing**: Jest (backend + frontend)
|
||||
- **Docker-First**: All development in containers (production-only)
|
||||
|
||||
## Agent Coordination Rules
|
||||
|
||||
### Clear Ownership
|
||||
- Feature Capsule Agent: Backend application features
|
||||
- Platform Feature Agent: Platform capsule inside backend
|
||||
- Mobile-First Frontend Agent: All UI/UX code
|
||||
- Quality Enforcer Agent: Testing and validation only
|
||||
|
||||
### Handoff Protocol
|
||||
1. Development agent completes work
|
||||
2. Development agent hands off to Quality Enforcer
|
||||
3. Quality Enforcer validates all quality gates
|
||||
4. Quality Enforcer reports pass/fail
|
||||
5. If fail: Development agent fixes issues
|
||||
6. If pass: Expert Software Architect approves
|
||||
|
||||
### Parallel Development
|
||||
- Feature Capsule + Mobile-First work simultaneously
|
||||
- Both agents have clear boundaries
|
||||
- Both hand off to Quality Enforcer when ready
|
||||
- Quality Enforcer validates complete feature
|
||||
|
||||
## Current Task
|
||||
|
||||
[Describe your specific task here - e.g., "Add notes field to vehicle form", "Create maintenance reminders feature", "Integrate new platform service"]
|
||||
|
||||
**Recommended Agent**: [Which agent should handle this task]
|
||||
|
||||
**Steps**:
|
||||
1. [Step 1]
|
||||
2. [Step 2]
|
||||
3. [Step 3]
|
||||
|
||||
## References
|
||||
|
||||
- Agent Definitions: `.claude/agents/`
|
||||
- Architecture: `docs/PLATFORM-SERVICES.md`
|
||||
- Testing: `docs/TESTING.md`
|
||||
- Context Loading: `.ai/context.json`
|
||||
- Development Guidelines: `CLAUDE.md`
|
||||
- Feature Documentation: `backend/src/features/{feature}/README.md`
|
||||
|
||||
@@ -136,7 +136,7 @@ npm test -- vehicles.service.test.ts
|
||||
npm test -- --testNamePattern="VIN validation"
|
||||
|
||||
# Frontend tests (Jest)
|
||||
make test-frontend
|
||||
docker compose exec mvp-frontend npm test
|
||||
```
|
||||
|
||||
### Coverage Reports
|
||||
@@ -271,7 +271,7 @@ docker compose exec mvp-frontend npm test
|
||||
### Feature Development Workflow
|
||||
1. **Write tests first**: TDD approach preferred
|
||||
2. **Run tests continuously**: Use `npm run test:watch`
|
||||
3. **Test in containers**: Always verify with `make test`
|
||||
3. **Test in containers**: Always verify with `docker compose exec mvp-backend npm test`
|
||||
4. **Check coverage**: Ensure new code is covered
|
||||
5. **Integration last**: Run full test suite before PR
|
||||
|
||||
|
||||
@@ -19,27 +19,30 @@ This document explains the end‑to‑end Vehicles API architecture after the pl
|
||||
|
||||
Idempotent constraints/indexes added where applicable (e.g., unique lower(name), unique(model_id, year), guarded `CREATE INDEX IF NOT EXISTS`, guarded trigger).
|
||||
|
||||
### API Endpoints (Bearer auth required)
|
||||
Prefix: `/api/platform`
|
||||
- `GET /api/platform/years` → `[number]` distinct years (desc)
|
||||
- `GET /api/platform/makes?year={year}` → `{ makes: { id, name }[] }`
|
||||
- `GET /api/platform/models?year={year}&make_id={make_id}` → `{ models: { id, name }[] }`
|
||||
- `GET /api/platform/trims?year={year}&make_id={make_id}&model_id={model_id}` → `{ trims: { id, name }[] }`
|
||||
- `GET /api/platform/engines?year={year}&make_id={make_id}&model_id={model_id}&trim_id={trim_id}` → `{ engines: { id, name }[] }`
|
||||
### 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`)
|
||||
|
||||
Notes:
|
||||
- `make_id` is maintained for a consistent query chain, but engines are enforced by `(year, model_id, trim_id)`.
|
||||
- Trims/engines include `id` to enable the next hop in the UI.
|
||||
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.).
|
||||
|
||||
### 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}`
|
||||
|
||||
### Authentication
|
||||
- Auth0 JWT via `Authorization: Bearer ${JWT_TOKEN}` (required for all platform endpoints)
|
||||
- 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: `dropdown:years`, `dropdown:makes:{year}`, `dropdown:models:{year}:{make}`, `dropdown:trims:{year}:{model}`, `dropdown:engines:{year}:{model}:{trim}`
|
||||
- Dropdown data TTL: 6 hours (21600 seconds)
|
||||
- VIN decode cache TTL: 7 days (604800 seconds)
|
||||
- Cache key format for VIN decodes: `vin:decode:{vin}`
|
||||
- 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`
|
||||
|
||||
### Seeds & Specific Examples
|
||||
@@ -56,22 +59,12 @@ Clear platform cache:
|
||||
## MotoVaultPro Backend (Application Service)
|
||||
|
||||
### Proxy Dropdown Endpoints
|
||||
Prefix: `/api/vehicles/dropdown`
|
||||
- `GET /years` → `[number]` (calls platform `/years`)
|
||||
- `GET /makes?year=YYYY` → `{ id, name }[]`
|
||||
- `GET /models?year=YYYY&make_id=ID` → `{ id, name }[]`
|
||||
- `GET /trims?year=YYYY&make_id=ID&model_id=ID` → `{ id, name }[]`
|
||||
- `GET /engines?year=YYYY&make_id=ID&model_id=ID&trim_id=ID` → `{ id, name }[]`
|
||||
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.
|
||||
|
||||
Changes:
|
||||
- Engines route now requires `trim_id`.
|
||||
- New `/years` route for UI bootstrap.
|
||||
|
||||
### Platform Client & Integration
|
||||
- `PlatformVehiclesClient`:
|
||||
- Added `getYears()`
|
||||
- `getEngines(year, makeId, modelId, trimId)` to pass trim id
|
||||
- `PlatformIntegrationService` consumed by `VehiclesService` updated accordingly.
|
||||
### 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.
|
||||
|
||||
### Authentication (App)
|
||||
- Auth0 JWT enforced via Fastify + JWKS. No mock users.
|
||||
@@ -94,6 +87,7 @@ Changes:
|
||||
- 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)
|
||||
|
||||
@@ -135,7 +129,7 @@ VIN/License rule
|
||||
- Backend (includes platform module): `docker compose up -d --build backend`
|
||||
|
||||
### Logs & Health
|
||||
- Backend: `/health` – shows status/feature list, including platform readiness
|
||||
- Backend: `https://motovaultpro.com/api/health` – shows status/feature list, including platform readiness
|
||||
- Logs: `make logs-backend`, `make logs-frontend`
|
||||
|
||||
### Common Reset Sequences
|
||||
|
||||
@@ -890,7 +890,7 @@ const sortedStations = useMemo(() => {
|
||||
|
||||
**Solutions**:
|
||||
1. Check browser permissions (user must allow)
|
||||
2. Requires HTTPS in production (not localhost)
|
||||
2. Requires HTTPS in production (use https://motovaultpro.com)
|
||||
3. Some browsers block geolocation in iframes
|
||||
4. Fallback to manual coordinates
|
||||
|
||||
|
||||
@@ -32,34 +32,34 @@ export const vehiclesApi = {
|
||||
await apiClient.delete(`/vehicles/${id}`);
|
||||
},
|
||||
|
||||
// Dropdown API methods (authenticated) - using unified platform endpoints
|
||||
// Dropdown API methods (authenticated) - proxied through vehicles capsule
|
||||
getYears: async (): Promise<number[]> => {
|
||||
const response = await apiClient.get('/platform/years');
|
||||
const response = await apiClient.get('/vehicles/dropdown/years');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMakes: async (year: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/platform/makes?year=${year}`);
|
||||
const response = await apiClient.get(`/vehicles/dropdown/makes?year=${year}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getModels: async (year: number, makeId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/platform/models?year=${year}&make_id=${makeId}`);
|
||||
const response = await apiClient.get(`/vehicles/dropdown/models?year=${year}&make_id=${makeId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTransmissions: async (year: number, makeId: number, modelId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/platform/transmissions?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
const response = await apiClient.get(`/vehicles/dropdown/transmissions?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getEngines: async (year: number, makeId: number, modelId: number, trimId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/platform/engines?year=${year}&make_id=${makeId}&model_id=${modelId}&trim_id=${trimId}`);
|
||||
const response = await apiClient.get(`/vehicles/dropdown/engines?year=${year}&make_id=${makeId}&model_id=${modelId}&trim_id=${trimId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTrims: async (year: number, makeId: number, modelId: number): Promise<DropdownOption[]> => {
|
||||
const response = await apiClient.get(`/platform/trims?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
const response = await apiClient.get(`/vehicles/dropdown/trims?year=${year}&make_id=${makeId}&model_id=${modelId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const [models, setModels] = useState<DropdownOption[]>([]);
|
||||
const [engines, setEngines] = useState<DropdownOption[]>([]);
|
||||
const [trims, setTrims] = useState<DropdownOption[]>([]);
|
||||
const [transmissions, setTransmissions] = useState<DropdownOption[]>([]);
|
||||
const [selectedYear, setSelectedYear] = useState<number | undefined>();
|
||||
const [selectedMake, setSelectedMake] = useState<DropdownOption | undefined>();
|
||||
const [selectedModel, setSelectedModel] = useState<DropdownOption | undefined>();
|
||||
@@ -155,6 +156,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
setModels([]);
|
||||
setEngines([]);
|
||||
setTrims([]);
|
||||
setTransmissions([]);
|
||||
setSelectedMake(undefined);
|
||||
setSelectedModel(undefined);
|
||||
setValue('make', '');
|
||||
@@ -189,6 +191,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
// Clear dependent selections
|
||||
setEngines([]);
|
||||
setTrims([]);
|
||||
setTransmissions([]);
|
||||
setSelectedModel(undefined);
|
||||
setValue('model', '');
|
||||
setValue('transmission', '');
|
||||
@@ -215,8 +218,12 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
const loadTrims = async () => {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const trimsData = await vehiclesApi.getTrims(watchedYear, selectedMake.id, modelOption.id);
|
||||
const [trimsData, transmissionsData] = await Promise.all([
|
||||
vehiclesApi.getTrims(watchedYear, selectedMake.id, modelOption.id),
|
||||
vehiclesApi.getTransmissions(watchedYear, selectedMake.id, modelOption.id)
|
||||
]);
|
||||
setTrims(trimsData);
|
||||
setTransmissions(transmissionsData);
|
||||
setSelectedModel(modelOption);
|
||||
// Clear deeper selections
|
||||
setEngines([]);
|
||||
@@ -228,6 +235,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
console.error('Failed to load detailed data:', error);
|
||||
setTrims([]);
|
||||
setEngines([]);
|
||||
setTransmissions([]);
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
}
|
||||
@@ -397,7 +405,7 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Transmission (right, static options) */}
|
||||
{/* Transmission (right) */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Transmission
|
||||
@@ -405,11 +413,15 @@ export const VehicleForm: React.FC<VehicleFormProps> = ({
|
||||
<select
|
||||
{...register('transmission')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-[44px]"
|
||||
disabled={loadingDropdowns || transmissions.length === 0}
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
<option value="">Select Transmission</option>
|
||||
<option value="Automatic">Automatic</option>
|
||||
<option value="Manual">Manual</option>
|
||||
{transmissions.map((transmission) => (
|
||||
<option key={transmission.id} value={transmission.name}>
|
||||
{transmission.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user