From 0c3ed01f4b8e698efecfe1951efa488b9430180f Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:04:48 -0600 Subject: [PATCH] Pre-web changes --- .env.development | 20 - IMPLEMENTATION-SUMMARY.md | 14 +- Makefile | 20 +- backend/src/features/platform/index.ts | 10 + backend/src/features/stations/README.md | 4 +- backend/src/features/stations/docs/API.md | 14 +- .../stations/docs/GOOGLE-MAPS-SETUP.md | 6 +- .../docs/deployment/DEPLOYMENT-CHECKLIST.md | 32 +- .../stations/docs/deployment/HEALTH-CHECKS.md | 30 +- .../docs/deployment/PRODUCTION-READINESS.md | 2 +- .../docs/deployment/SECRETS-VERIFICATION.md | 6 +- backend/src/features/vehicles/README.md | 19 +- .../vehicles/domain/vehicles.service.ts | 72 +- .../tests/unit/vehicles.service.test.ts | 85 +- docs/ARCHITECTURE-OVERVIEW.md | 6 +- docs/GAS-STATION-AGENTS.md | 1118 ----------------- docs/GAS-STATIONS-TESTING-REPORT.md | 295 ----- docs/GAS-STATIONS.md | 648 ---------- docs/MAINTENANCE-FEATURE-PLAN.md | 1037 --------------- docs/PROMPTS.md | 261 +--- docs/TESTING.md | 4 +- docs/VEHICLES-API.md | 56 +- frontend/src/features/stations/README.md | 2 +- .../src/features/vehicles/api/vehicles.api.ts | 14 +- .../vehicles/components/VehicleForm.tsx | 20 +- 25 files changed, 257 insertions(+), 3538 deletions(-) delete mode 100644 .env.development delete mode 100644 docs/GAS-STATION-AGENTS.md delete mode 100644 docs/GAS-STATIONS-TESTING-REPORT.md delete mode 100644 docs/GAS-STATIONS.md delete mode 100644 docs/MAINTENANCE-FEATURE-PLAN.md diff --git a/.env.development b/.env.development deleted file mode 100644 index 2d14e55..0000000 --- a/.env.development +++ /dev/null @@ -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) \ No newline at end of file diff --git a/IMPLEMENTATION-SUMMARY.md b/IMPLEMENTATION-SUMMARY.md index e886277..9c22224 100644 --- a/IMPLEMENTATION-SUMMARY.md +++ b/IMPLEMENTATION-SUMMARY.md @@ -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 diff --git a/Makefile b/Makefile index 9a45f4b..06ffee2 100644 --- a/Makefile +++ b/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: diff --git a/backend/src/features/platform/index.ts b/backend/src/features/platform/index.ts index b124401..cc4d81a 100644 --- a/backend/src/features/platform/index.ts +++ b/backend/src/features/platform/index.ts @@ -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; diff --git a/backend/src/features/stations/README.md b/backend/src/features/stations/README.md index c2049b1..1ea716f 100644 --- a/backend/src/features/stations/README.md +++ b/backend/src/features/stations/README.md @@ -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) diff --git a/backend/src/features/stations/docs/API.md b/backend/src/features/stations/docs/API.md index 4ae071a..fdbbeca 100644 --- a/backend/src/features/stations/docs/API.md +++ b/backend/src/features/stations/docs/API.md @@ -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 diff --git a/backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md b/backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md index 9d02e03..3afa468 100644 --- a/backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md +++ b/backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md @@ -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}' diff --git a/backend/src/features/stations/docs/deployment/DEPLOYMENT-CHECKLIST.md b/backend/src/features/stations/docs/deployment/DEPLOYMENT-CHECKLIST.md index 0f688d2..29bcf24 100644 --- a/backend/src/features/stations/docs/deployment/DEPLOYMENT-CHECKLIST.md +++ b/backend/src/features/stations/docs/deployment/DEPLOYMENT-CHECKLIST.md @@ -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 diff --git a/backend/src/features/stations/docs/deployment/HEALTH-CHECKS.md b/backend/src/features/stations/docs/deployment/HEALTH-CHECKS.md index b178370..15acafb 100644 --- a/backend/src/features/stations/docs/deployment/HEALTH-CHECKS.md +++ b/backend/src/features/stations/docs/deployment/HEALTH-CHECKS.md @@ -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 diff --git a/backend/src/features/stations/docs/deployment/PRODUCTION-READINESS.md b/backend/src/features/stations/docs/deployment/PRODUCTION-READINESS.md index 9c7b82f..f3a44a3 100644 --- a/backend/src/features/stations/docs/deployment/PRODUCTION-READINESS.md +++ b/backend/src/features/stations/docs/deployment/PRODUCTION-READINESS.md @@ -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*` diff --git a/backend/src/features/stations/docs/deployment/SECRETS-VERIFICATION.md b/backend/src/features/stations/docs/deployment/SECRETS-VERIFICATION.md index 411e715..94606f2 100644 --- a/backend/src/features/stations/docs/deployment/SECRETS-VERIFICATION.md +++ b/backend/src/features/stations/docs/deployment/SECRETS-VERIFICATION.md @@ -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**: diff --git a/backend/src/features/vehicles/README.md b/backend/src/features/vehicles/README.md index 46a5604..24e4a80 100644 --- a/backend/src/features/vehicles/README.md +++ b/backend/src/features/vehicles/README.md @@ -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). diff --git a/backend/src/features/vehicles/domain/vehicles.service.ts b/backend/src/features/vehicles/domain/vehicles.service.ts index 1f684b0..8d79a32 100644 --- a/backend/src/features/vehicles/domain/vehicles.service.ts +++ b/backend/src/features/vehicles/domain/vehicles.service.ts @@ -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 { - 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<{ diff --git a/backend/src/features/vehicles/tests/unit/vehicles.service.test.ts b/backend/src/features/vehicles/tests/unit/vehicles.service.test.ts index 23a3a21..7a747f3 100644 --- a/backend/src/features/vehicles/tests/unit/vehicles.service.test.ts +++ b/backend/src/features/vehicles/tests/unit/vehicles.service.test.ts @@ -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; + 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', @@ -319,4 +398,4 @@ describe('VehiclesService', () => { await expect(service.deleteVehicle('vehicle-id-123', 'user-123')).rejects.toThrow('Unauthorized'); }); }); -}); \ No newline at end of file +}); diff --git a/docs/ARCHITECTURE-OVERVIEW.md b/docs/ARCHITECTURE-OVERVIEW.md index 5ff39ea..b816fe5 100644 --- a/docs/ARCHITECTURE-OVERVIEW.md +++ b/docs/ARCHITECTURE-OVERVIEW.md @@ -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 diff --git a/docs/GAS-STATION-AGENTS.md b/docs/GAS-STATION-AGENTS.md deleted file mode 100644 index 45a1b56..0000000 --- a/docs/GAS-STATION-AGENTS.md +++ /dev/null @@ -1,1118 +0,0 @@ -# Gas Stations Feature - Parallel Agent Execution Plan - -## Overview - -This document contains agent definition prompts for implementing the Gas Stations feature using parallel execution. The backend API is complete; this plan focuses on frontend implementation, backend improvements, and integration. - -### Key Context -- Backend API: Fully implemented at `backend/src/features/stations/` -- Frontend: Completely missing (needs full implementation) -- Database: Schema ready, needs migration execution -- Secrets: Google Maps API key exists in `secrets/app/google-maps-api-key.txt` -- Pattern: Follow K8s-aligned runtime secrets (not build-time env vars) -- Reference: Use `frontend/src/features/vehicles/` as implementation pattern - -### Investigation Summary -- Backend has 4 complete API endpoints (search, save, get saved, delete) -- Database migration ready: `001_create_stations_tables.sql` -- External integration: Google Maps Places API client implemented -- No frontend code exists yet (placeholder route only) -- No tests exist (empty directories) -- No circuit breaker or cache cleanup implemented - -## Execution Phases - -``` -Phase 1 (Parallel - Infrastructure): -├─ Agent 1: Frontend-Secrets-Agent -└─ Agent 2: Backend-Reliability-Agent - -Phase 2 (Foundation): -└─ Agent 3: Frontend-Foundation-Agent (depends on Agent 1) - -Phase 3 (Parallel - Components): -├─ Agent 4: Components-Agent (depends on Agent 3) -└─ Agent 5: Map-Visualization-Agent (depends on Agent 3) - -Phase 4 (Parallel - Pages): -├─ Agent 6: Desktop-Mobile-Agent (depends on Agents 4, 5) -└─ Agent 7: Integration-Agent (depends on Agents 4, 5) - -Phase 5 (Testing): -└─ Agent 8: Testing-Documentation-Agent (depends on all previous) -``` - ---- - -## Agent 1: Frontend-Secrets-Agent - -### Scope -Implement K8s-aligned runtime secrets pattern for frontend to load Google Maps API key from `/run/secrets/` at container startup. - -### Prerequisites -- None (can start immediately) - -### Responsibilities - -1. **Create Frontend Config Loader Script** - - File: `frontend/scripts/load-config.sh` - - Reads `/run/secrets/google-maps-api-key` - - Generates `/usr/share/nginx/html/config.js` with: `window.CONFIG = { googleMapsApiKey: 'KEY_VALUE' }` - - Fallback to empty string if secret not found - - Logs success/failure for debugging - -2. **Update Frontend Dockerfile** - - File: `frontend/Dockerfile` - - Copy `scripts/load-config.sh` into image - - Make script executable - - Add `SECRETS_DIR` environment variable (default: `/run/secrets`) - - Update CMD to: `["sh", "-c", "/app/load-config.sh && nginx -g 'daemon off;'"]` - -3. **Update index.html** - - File: `frontend/index.html` - - Add `` before app bundle script tag - - Ensure config loads synchronously before React initializes - -4. **Create Frontend Config Types** - - File: `frontend/src/core/config/config.types.ts` - - Interface: `RuntimeConfig { googleMapsApiKey: string }` - - Extend Window interface: `declare global { interface Window { CONFIG: RuntimeConfig } }` - - Export typed accessor: `export function getConfig(): RuntimeConfig` - - Runtime validation: throw if required values missing - -5. **Update docker-compose.yml** - - File: `docker-compose.yml` - - Add to `mvp-frontend` service volumes: - ```yaml - - ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro - ``` - - Add environment variable: `SECRETS_DIR: /run/secrets` - -6. **Create Documentation** - - File: `frontend/docs/RUNTIME-CONFIG.md` - - Explain runtime config pattern vs build-time - - How to add new runtime secrets - - K8s deployment notes - - Local development setup - -### Success Criteria -- Frontend container starts successfully -- `/usr/share/nginx/html/config.js` exists and contains API key -- `getConfig()` returns typed RuntimeConfig object -- No TypeScript errors -- Linters pass - -### Handoff Notes -Provides `getConfig().googleMapsApiKey` for Agent 5 (Map-Visualization-Agent) to use when loading Google Maps JavaScript API. - ---- - -## Agent 2: Backend-Reliability-Agent - -### Scope -Enhance backend reliability with circuit breaker, comprehensive tests, and cache cleanup job. - -### Prerequisites -- None (can start immediately) - -### Responsibilities - -1. **Add Circuit Breaker Pattern** - - Install package: Add `"opossum": "^8.1.4"` to `backend/package.json` - - File: `backend/src/features/stations/external/google-maps/google-maps.circuit-breaker.ts` - - Create circuit breaker factory function - - Configuration: 10s timeout, 50% error threshold, 30s reset timeout - - Update: `backend/src/features/stations/external/google-maps/google-maps.client.ts` - - Wrap `searchNearby()` method with circuit breaker - - Follow pattern from `backend/src/features/platform/` (reference implementation) - - Add circuit breaker events logging (open, close, halfOpen) - -2. **Backend Unit Tests** - - File: `backend/src/features/stations/tests/unit/stations.service.test.ts` - - Test searchStations() with valid coordinates - - Test saveStation() with user_id isolation - - Test getSavedStations() returns only user's stations - - Test deleteSavedStation() removes correct station - - File: `backend/src/features/stations/tests/unit/stations.repository.test.ts` - - Test database queries with mock connection - - Test SQL injection protection - - Test constraint violations (duplicate saves) - - File: `backend/src/features/stations/tests/unit/google-maps.client.test.ts` - - Mock Google Maps API responses - - Test distance calculation (Haversine formula) - - Test photo URL generation - - Test error handling - -3. **Backend Integration Tests** - - File: `backend/src/features/stations/tests/integration/stations.api.test.ts` - - Test all 4 endpoints with real database - - Test JWT authentication requirement - - Test request validation (Zod schemas) - - Test error responses (400, 401, 500) - - File: `backend/src/features/stations/tests/integration/saved-stations.flow.test.ts` - - Test complete workflow: search → save → retrieve → delete - - Test duplicate save handling - - Test user isolation (user A can't see user B's stations) - -4. **Test Fixtures** - - File: `backend/src/features/stations/tests/fixtures/mock-stations.ts` - - Export sample Station objects (5-10 examples) - - Include various ratings, distances, prices - - File: `backend/src/features/stations/tests/fixtures/mock-google-response.ts` - - Mock Google Places API responses - - Include success and error cases - - File: `backend/src/features/stations/tests/fixtures/test-coordinates.ts` - - Sample coordinates for major cities - - Edge cases (equator, poles, date line) - -5. **Cache Cleanup Job** - - File: `backend/src/features/stations/jobs/cache-cleanup.job.ts` - - Scheduled job using cron (daily at 2 AM) - - Delete `station_cache` entries older than 24 hours - - Log metrics: rows deleted, execution time - - Error handling and retry logic - - Update: `backend/src/app.ts` - - Import and register cache cleanup job - - Add to scheduler (if exists) or create simple interval - -6. **Run Tests** - - Execute: `cd backend && npm test -- features/stations` - - Verify 100% pass rate - - Check coverage (aim for >80%) - -### Success Criteria -- Circuit breaker implemented and tested -- All unit tests pass -- All integration tests pass -- Test coverage >80% -- Cache cleanup job registered -- Linters pass -- No TypeScript errors - -### Handoff Notes -Backend is now production-ready with reliability patterns. Tests provide safety net for future changes. - ---- - -## Agent 3: Frontend-Foundation-Agent - -### Scope -Create frontend foundation: types, API client, and React Query hooks for stations feature. - -### Prerequisites -- **Depends on Agent 1** (needs `getConfig()` function) - -### Responsibilities - -1. **Type Definitions** - - File: `frontend/src/features/stations/types/stations.types.ts` - - Interface `Station`: - - placeId: string - - name: string - - address: string - - location: { latitude: number; longitude: number } - - rating?: number - - distance: number (meters) - - photoUrl?: string - - prices?: { fuel_type: string; price: number }[] - - Interface `SavedStation` extends Station: - - userId: string - - nickname?: string - - notes?: string - - isFavorite: boolean - - savedAt: string - - Interface `SearchRequest`: - - latitude: number - - longitude: number - - radius?: number (meters, default 5000) - - fuelType?: string - - Interface `SearchResponse`: - - stations: Station[] - - searchLocation: { latitude: number; longitude: number } - - Interface `SaveStationData`: - - placeId: string - - nickname?: string - - notes?: string - - isFavorite?: boolean - -2. **API Client** - - File: `frontend/src/features/stations/api/stations.api.ts` - - Function `searchStations(request: SearchRequest): Promise` - - POST /api/stations/search - - Include JWT in Authorization header - - Return stations array - - Function `saveStation(data: SaveStationData): Promise` - - POST /api/stations/save - - Include JWT in Authorization header - - Return saved station with full details - - Function `getSavedStations(): Promise` - - GET /api/stations/saved - - Include JWT in Authorization header - - Return array of user's saved stations - - Function `deleteSavedStation(placeId: string): Promise` - - DELETE /api/stations/saved/:placeId - - Include JWT in Authorization header - - Return nothing (204 No Content) - - Use axios instance from `frontend/src/core/api/client.ts` - - Proper error handling with try-catch - - Add request/response logging for debugging - -3. **React Query Hooks** - - File: `frontend/src/features/stations/hooks/useStationsSearch.ts` - - Export `useStationsSearch()` mutation hook - - Accepts SearchRequest, returns Station[] - - Don't cache by default (location-specific queries) - - Loading/error states - - File: `frontend/src/features/stations/hooks/useSavedStations.ts` - - Export `useSavedStations()` query hook - - Query key: `['stations', 'saved']` - - Auto-refetch on window focus - - Stale time: 5 minutes - - Loading/error states - - File: `frontend/src/features/stations/hooks/useSaveStation.ts` - - Export `useSaveStation()` mutation hook - - Accepts SaveStationData - - Optimistic update: add to saved list immediately - - On success: invalidate `['stations', 'saved']` query - - On error: rollback optimistic update - - File: `frontend/src/features/stations/hooks/useDeleteStation.ts` - - Export `useDeleteStation()` mutation hook - - Accepts placeId string - - Optimistic update: remove from saved list - - On success: invalidate `['stations', 'saved']` query - - On error: rollback optimistic update - - File: `frontend/src/features/stations/hooks/useGeolocation.ts` - - Export `useGeolocation()` hook - - Uses browser Geolocation API - - Returns: `{ position, loading, error, requestLocation }` - - Handle permission states: prompt, granted, denied - - Error handling: timeout, unavailable, permission denied - - Options: enableHighAccuracy, timeout, maximumAge - -4. **Utilities** - - File: `frontend/src/features/stations/utils/distance.ts` - - Export `formatDistance(meters: number): string` - - Convert meters to miles with proper formatting - - Examples: "0.3 mi", "1.2 mi", "5.7 mi" - - File: `frontend/src/features/stations/utils/location.ts` - - Export `getCurrentPosition(): Promise` - - Wrapper around navigator.geolocation.getCurrentPosition - - Promisified API - - Error handling helpers - -### Success Criteria -- All type interfaces defined with proper TypeScript types -- API client functions work (test with curl against backend) -- All React Query hooks created with proper caching -- Geolocation hook handles all permission states -- No TypeScript errors -- Linters pass -- Utilities properly format data - -### Handoff Notes -Foundation is complete. Agent 4 and Agent 5 can now build components and map visualization using these types, API client, and hooks. - ---- - -## Agent 4: Components-Agent - -### Scope -Build all React components for stations feature (cards, lists, forms). - -### Prerequisites -- **Depends on Agent 3** (needs types, hooks, API client) - -### Responsibilities - -1. **StationCard Component** - - File: `frontend/src/features/stations/components/StationCard.tsx` - - Props: - - station: Station - - isSaved?: boolean - - onSave?: (placeId: string) => void - - onDelete?: (placeId: string) => void - - onSelect?: (station: Station) => void - - UI Elements: - - Material-UI Card component - - Station name (Typography variant="h6") - - Address (Typography variant="body2", color="text.secondary") - - Distance chip (Chip with LocationOnIcon) - - Rating stars (Rating component, if rating exists) - - Price display (Typography, if prices available) - - Photo thumbnail (CardMedia, if photoUrl exists) - - Save/Unsave IconButton (BookmarkIcon / BookmarkBorderIcon) - - Directions link (Button with OpenInNewIcon, opens Google Maps) - - Styling: - - Responsive: full width on mobile, fixed width on desktop - - Min height 44px for touch targets - - Hover effects on desktop - - Loading state while saving/deleting - - Follow pattern: `frontend/src/features/vehicles/components/VehicleCard.tsx` - -2. **StationsList Component** - - File: `frontend/src/features/stations/components/StationsList.tsx` - - Props: - - stations: Station[] - - loading?: boolean - - error?: Error | null - - onSaveStation?: (placeId: string) => void - - savedStationIds?: Set - - UI Elements: - - Grid container (responsive) - - Layout: 1 col mobile, 2 cols tablet, 3 cols desktop - - Loading state: Skeleton components (3-6 skeletons) - - Empty state: "No stations found. Try adjusting your search." - - Error state: Alert with retry option - - Map stations to StationCard components - - Styling: - - Gap between cards: 16px - - Smooth loading transitions - - Follow pattern: `frontend/src/features/vehicles/components/VehiclesList.tsx` - -3. **SavedStationsList Component** - - File: `frontend/src/features/stations/components/SavedStationsList.tsx` - - Props: - - savedStations: SavedStation[] - - loading?: boolean - - error?: Error | null - - onDelete: (placeId: string) => void - - onSelect?: (station: SavedStation) => void - - currentLocation?: { latitude: number; longitude: number } - - UI Elements: - - List container (vertical) - - Each item shows: nickname || name, address, distance from current location - - Notes preview (truncated to 50 chars) - - Favorite icon if isFavorite - - Delete IconButton with confirmation dialog - - Loading state: Linear progress bar - - Empty state: "No saved stations yet. Save stations from search results." - - Error state: Alert with error message - - Styling: - - List item min height: 64px - - Dividers between items - - Delete button always visible (not just on hover for mobile) - - Follow pattern: Material-UI List component examples - -4. **StationsSearchForm Component** - - File: `frontend/src/features/stations/components/StationsSearchForm.tsx` - - Props: - - onSearch: (request: SearchRequest) => void - - loading?: boolean - - UI Elements: - - Form container (Card or Paper) - - "Use Current Location" Button (uses useGeolocation hook) - - OR divider - - Manual lat/lng inputs (TextField type="number") - - Radius slider (Slider: 1-25 miles, default 5, step 1) - - Fuel type select (optional, Select component) - - Search LoadingButton (shows loading state during search) - - Geolocation status indicator (permission state) - - Error display for geolocation failures - - Validation: - - Require either current location OR manual lat/lng - - Validate latitude: -90 to 90 - - Validate longitude: -180 to 180 - - Behavior: - - Click "Use Current Location" → request permission → populate hidden lat/lng - - Manual input disables current location - - Convert miles to meters for API (1 mile = 1609.34 meters) - - Styling: - - Compact form layout - - Responsive: stack vertically on mobile - - Clear visual feedback for loading states - - Follow pattern: `frontend/src/features/fuel-logs/components/FuelLogForm.tsx` - -5. **Create Feature Index** - - File: `frontend/src/features/stations/components/index.ts` - - Export all components for easy importing - -### Success Criteria -- All 4 components render without errors -- Components follow Material-UI design patterns -- Mobile responsive (test at 375px width) -- Touch targets minimum 44px -- Loading and error states work -- TypeScript types are correct -- Linters pass -- Components match vehicles feature pattern - -### Handoff Notes -Core components are ready. Agent 6 can now build pages using these components. Agent 7 can integrate with map visualization. - ---- - -## Agent 5: Map-Visualization-Agent - -### Scope -Implement Google Maps integration and StationMap component for visualizing stations on a map. - -### Prerequisites -- **Depends on Agent 1** (needs `getConfig().googleMapsApiKey`) -- **Depends on Agent 3** (needs Station types) - -### Responsibilities - -1. **Google Maps API Loader** - - File: `frontend/src/features/stations/utils/maps-loader.ts` - - Function: `loadGoogleMapsAPI(): Promise` - - Load Google Maps JavaScript API dynamically - - Use runtime config: `getConfig().googleMapsApiKey` - - Include libraries: `libraries=places,geometry` - - Singleton pattern: only load once, return cached promise - - Error handling: throw if API key missing or script fails - - TypeScript: Use `@types/google.maps` for types - -2. **Map Utilities** - - File: `frontend/src/features/stations/utils/map-utils.ts` - - Function: `createStationMarker(station: Station, map: google.maps.Map, isSaved: boolean): google.maps.Marker` - - Create marker at station location - - Icon: blue pin for normal, gold star for saved - - Title: station name - - Clickable: true - - Function: `createInfoWindow(station: Station, isSaved: boolean, onSave: () => void, onDirections: () => void): google.maps.InfoWindow` - - Create info window with station details - - Content: name, address, rating, distance - - Action buttons: Save/Saved, Directions - - HTML string with proper escaping - - Function: `fitBoundsToMarkers(map: google.maps.Map, markers: google.maps.Marker[]): void` - - Calculate bounds containing all markers - - Fit map to bounds with padding - - Handle single marker case (zoom to 14) - - Function: `calculateCenter(stations: Station[]): google.maps.LatLng` - - Calculate geometric center of stations - - Return LatLng object - -3. **StationMap Component** - - File: `frontend/src/features/stations/components/StationMap.tsx` - - Props: - - stations: Station[] - - savedStationIds?: Set - - center?: { latitude: number; longitude: number } - - zoom?: number - - currentLocation?: { latitude: number; longitude: number } - - onStationClick?: (station: Station) => void - - onSaveStation?: (placeId: string) => void - - State: - - map: google.maps.Map | null - - markers: google.maps.Marker[] - - infoWindow: google.maps.InfoWindow | null - - Behavior: - - Load Google Maps API on mount (useEffect) - - Initialize map in div container - - Create markers for all stations - - Current location marker (red pin, if provided) - - Click marker → open info window - - Click save button → call onSaveStation - - Click directions → open Google Maps in new tab - - Auto-fit bounds to show all markers - - Clean up markers on unmount - - Styling: - - Container: width 100%, height configurable (default 500px) - - Responsive: 300px height on mobile, 500px on desktop - - Map controls: zoom, pan, fullscreen - - Custom map styles (optional: subtle styling) - - Map Options: - - mapTypeControl: false - - streetViewControl: false - - fullscreenControl: true - - zoomControl: true - - Error Handling: - - Show error message if API fails to load - - Fallback: display stations in list if map unavailable - -4. **TypeScript Types** - - Ensure `@types/google.maps` is in `frontend/package.json` - - Add if missing: `"@types/google.maps": "^3.54.0"` - -5. **Testing** - - Test map loads with valid API key - - Test markers render for each station - - Test info windows open on marker click - - Test current location marker (red pin) - - Test directions link opens correct URL - - Test error handling (invalid API key) - -### Success Criteria -- Google Maps API loads successfully from runtime config -- StationMap component renders map -- Markers appear for all stations -- Color coding works (blue vs gold for saved) -- Info windows display correct station details -- Directions links work -- Current location marker shows if provided -- Responsive sizing works -- No TypeScript errors -- Linters pass - -### Handoff Notes -Map visualization is complete. Agent 6 can integrate StationMap into desktop and mobile pages. - ---- - -## Agent 6: Desktop-Mobile-Agent - -### Scope -Build desktop StationsPage and mobile StationsMobileScreen, update routing. - -### Prerequisites -- **Depends on Agent 4** (needs all components) -- **Depends on Agent 5** (needs StationMap) - -### Responsibilities - -1. **Desktop StationsPage** - - File: `frontend/src/features/stations/pages/StationsPage.tsx` - - Layout: - - Container: Full width, Grid layout - - Left column (60%): StationMap (full height 600px) - - Right column (40%): - - StationsSearchForm at top - - Tabs component: "Search Results" | "Saved Stations" - - Tab 1 content: StationsList (scrollable) - - Tab 2 content: SavedStationsList (scrollable) - - State: - - searchResults: Station[] (from useStationsSearch) - - selectedStation: Station | null (for map focus) - - currentLocation: Position | null (from useGeolocation) - - activeTab: 0 | 1 - - Hooks: - - useStationsSearch() for searching - - useSavedStations() for saved list - - useSaveStation() for saving - - useDeleteStation() for deleting - - useGeolocation() for current location - - Behavior: - - On search submit → call searchStations mutation - - On search success → update map with results - - On save station → add to saved list (optimistic) - - On delete station → remove from saved list (optimistic) - - Click station card → highlight on map (zoom to marker) - - Map marker click → highlight card and scroll into view - - Responsive: - - Breakpoint <960px: Stack vertically (map on top) - - Mobile view: Map 300px height, full width - - Loading States: - - Show loading spinner during search - - Skeleton loaders for saved stations - - Error Handling: - - Display error alerts for API failures - - Retry buttons where appropriate - - Follow pattern: `frontend/src/features/vehicles/pages/VehiclesPage.tsx` - -2. **Mobile StationsScreen** - - File: `frontend/src/features/stations/pages/StationsMobileScreen.tsx` - - Layout: - - BottomNavigation with 3 tabs: "Search", "Saved", "Map" - - Tab content area (full screen minus bottom nav) - - Tab 0 (Search): - - StationsSearchForm (compact mode, vertical layout) - - StationsList (vertical scroll, full width) - - Pull-to-refresh support (optional) - - Tab 1 (Saved): - - SavedStationsList (full screen) - - Swipe-to-delete gestures (optional enhancement) - - Empty state with illustration - - Tab 2 (Map): - - StationMap (full screen, 100vh minus nav) - - Floating action button (FAB) to return to search tab - - Current location button (FAB) - - State: - - activeTab: 0 | 1 | 2 - - Same data state as desktop page - - Hooks: - - Same as desktop page - - Behavior: - - Tab switching persists scroll position - - Search → auto-switch to results view - - Save station → show snackbar confirmation - - Map marker click → open bottom sheet with station details - - Bottom Sheet: - - SwipeableDrawer component - - Shows station details - - Save/Delete buttons - - Directions link - - Touch Targets: - - All buttons minimum 44px - - Adequate spacing between interactive elements - - Safe Areas: - - Handle notched devices (safe-area-inset) - - Bottom navigation respects safe area - - Follow pattern: `frontend/src/features/vehicles/pages/VehiclesMobileScreen.tsx` - -3. **Update Desktop Routing** - - File: `frontend/src/App.tsx` (around line 556) - - Remove: `Stations (TODO)} />` - - Add: `} />` - - Import: `import { StationsPage } from './features/stations/pages/StationsPage'` - - Verify route is inside ProtectedRoute wrapper (requires auth) - -4. **Update Mobile Routing** - - File: `frontend/src/App.tsx` (mobile routes section) - - Add: `} />` - - Import: `import { StationsMobileScreen } from './features/stations/pages/StationsMobileScreen'` - - Update mobile navigation items: - - Add stations icon and route to bottom nav - - Icon: PlaceRoundedIcon (already used in Layout.tsx) - - Ensure onClick navigates to `/m/stations` - -5. **Feature Index** - - File: `frontend/src/features/stations/index.ts` - - Export StationsPage and StationsMobileScreen - - Export all components for external use - -6. **Manual Testing** - - Desktop: Navigate to /stations, test all functionality - - Mobile: Navigate to /m/stations, test all tabs - - Test on real mobile device or Chrome DevTools mobile emulation - - Verify responsive breakpoints work - - Verify touch targets are adequate (44px minimum) - -### Success Criteria -- Desktop page renders and functions completely -- Mobile screen renders with all 3 tabs -- Routing works for both desktop and mobile -- All components integrate properly -- Search, save, delete operations work -- Map updates when data changes -- Mobile touch targets meet 44px requirement -- Responsive design works at all breakpoints -- No TypeScript errors -- Linters pass - -### Handoff Notes -Desktop and mobile UIs are complete and fully functional. Users can now search, save, and view stations on both platforms. - ---- - -## Agent 7: Integration-Agent - -### Scope -Integrate stations feature with fuel logs using a station picker component. - -### Prerequisites -- **Depends on Agent 4** (needs StationCard and search functionality) -- **Depends on Agent 5** (needs map utilities) - -### Responsibilities - -1. **StationPicker Component** - - File: `frontend/src/features/fuel-logs/components/StationPicker.tsx` - - Props: - - value: string (station name) - - onChange: (stationName: string) => void - - userLocation?: { latitude: number; longitude: number } - - label?: string - - error?: boolean - - helperText?: string - - UI Elements: - - Autocomplete component (Material-UI) - - TextField with LocationOnIcon adornment - - Dropdown shows: saved stations (group 1) + nearby stations (group 2) - - Option rendering: - - Primary text: station name - - Secondary text: distance + address - - Bookmark icon if saved - - FreeSolo mode: allow manual text entry - - Loading indicator while fetching stations - - Behavior: - - On focus → fetch saved stations immediately - - If userLocation provided → fetch nearby stations (5 mile radius) - - Show saved stations first (grouped, labeled "Saved Stations") - - Show nearby stations second (grouped, labeled "Nearby Stations") - - On select → call onChange with station name - - On manual input → call onChange with entered text - - Debounce: 300ms delay for search queries - - Data: - - Use useSavedStations() hook - - Use useStationsSearch() hook with userLocation - - Combine results: saved + nearby - - De-duplicate by placeId - - Error Handling: - - If geolocation fails → only show saved stations - - If API fails → fallback to manual text input - - Display error message in helper text - - Follow pattern: Material-UI Autocomplete with grouping - -2. **Update FuelLogForm** - - File: `frontend/src/features/fuel-logs/components/FuelLogForm.tsx` - - Find: Current location input field (likely TextField or LocationInput component) - - Replace with: StationPicker component - - Props to pass: - - value: formData.stationName (or equivalent) - - onChange: (name) => setFormData({ ...formData, stationName: name }) - - userLocation: from useGeolocation hook - - label: "Gas Station" - - error: Boolean(errors.stationName) - - helperText: errors.stationName - - Integration: - - Add useGeolocation hook to FuelLogForm - - Pass current position to StationPicker - - Maintain existing form validation - - Ensure backward compatibility (text input still works) - -3. **Enhance Fuel Log Display (Optional)** - - File: `frontend/src/features/fuel-logs/components/FuelLogCard.tsx` - - If stationName is present → make it clickable - - On click → navigate to /stations with search for that name - - OR: Show station details in dialog - - Add LocationOnIcon next to station name - - Follow existing card styling - -4. **Add Geolocation to Fuel Logs** - - Update FuelLogForm to request location permission on mount - - Store current location in component state - - Pass to StationPicker for nearby stations - - Show permission request UI if needed - - Handle permission denied gracefully (picker still works with saved stations) - -5. **Testing** - - Test StationPicker in isolation (render component) - - Test selecting saved station - - Test selecting nearby station - - Test manual text entry (freeSolo) - - Test integration in FuelLogForm - - Test submitting fuel log with station - - Test geolocation permission flow - -### Success Criteria -- StationPicker component renders and functions -- Saved stations appear in dropdown -- Nearby stations appear in dropdown (if location available) -- Manual text entry works (fallback) -- FuelLogForm integrates StationPicker successfully -- Submitting fuel log with station works -- Backward compatible (existing fuel logs still display) -- Geolocation permission handled gracefully -- No TypeScript errors -- Linters pass - -### Handoff Notes -Fuel logs now integrate with stations feature. Users can select stations from saved or nearby lists when logging fuel, improving data consistency and user experience. - ---- - -## Agent 8: Testing-Documentation-Agent - -### Scope -Create comprehensive tests for all new code and complete documentation. - -### Prerequisites -- **Depends on all previous agents** (needs complete implementation) - -### Responsibilities - -1. **Backend Tests Verification** - - Run: `cd backend && npm test -- features/stations` - - Verify all tests pass (from Agent 2) - - If failures: debug and fix - - Check coverage: `npm test -- --coverage features/stations` - - Ensure coverage >80% - -2. **Frontend Component Tests** - - File: `frontend/src/features/stations/__tests__/components/StationCard.test.tsx` - - Test rendering with all props - - Test save button click - - Test delete button click - - Test directions link - - File: `frontend/src/features/stations/__tests__/components/StationsList.test.tsx` - - Test rendering with stations array - - Test loading state (skeletons) - - Test empty state - - Test error state - - File: `frontend/src/features/stations/__tests__/components/SavedStationsList.test.tsx` - - Test rendering saved stations - - Test delete action - - Test empty state - - File: `frontend/src/features/stations/__tests__/components/StationsSearchForm.test.tsx` - - Test form submission - - Test validation (lat/lng ranges) - - Test current location button - - Test manual input - - File: `frontend/src/features/stations/__tests__/components/StationMap.test.tsx` - - Mock Google Maps API - - Test map initialization - - Test marker rendering - - Test info window - - Use React Testing Library - - Mock hooks with jest.mock() - -3. **Frontend API Tests** - - File: `frontend/src/features/stations/api/__tests__/stations.api.test.ts` - - Test searchStations() - mock axios - - Test saveStation() - mock axios - - Test getSavedStations() - mock axios - - Test deleteSavedStation() - mock axios - - Test error handling (network errors, 401, 500) - - Verify request payloads - - Verify response parsing - -4. **Frontend Hook Tests** - - File: `frontend/src/features/stations/hooks/__tests__/useStationsSearch.test.ts` - - Test mutation flow - - Test loading state - - Test success state - - Test error state - - File: `frontend/src/features/stations/hooks/__tests__/useSavedStations.test.ts` - - Test query flow - - Test cache behavior - - Test refetch logic - - File: `frontend/src/features/stations/hooks/__tests__/useSaveStation.test.ts` - - Test mutation with optimistic update - - Test cache invalidation - - Test rollback on error - - File: `frontend/src/features/stations/hooks/__tests__/useDeleteStation.test.ts` - - Test mutation with optimistic update - - Test cache invalidation - - Test rollback on error - - Use @testing-library/react-hooks - - Mock React Query client - -5. **Frontend Page Tests** - - File: `frontend/src/features/stations/__tests__/pages/StationsPage.test.tsx` - - Test page renders - - Test search workflow - - Test save station workflow - - Test tab switching - - Test map interaction - - File: `frontend/src/features/stations/__tests__/pages/StationsMobileScreen.test.tsx` - - Test all 3 tabs render - - Test tab navigation - - Test search on mobile - - Test saved list on mobile - -6. **Integration Component Tests** - - File: `frontend/src/features/fuel-logs/__tests__/components/StationPicker.test.tsx` - - Test rendering - - Test saved stations load - - Test nearby stations load (with geolocation) - - Test manual text entry - - Test selection callback - - Mock useStationsSearch and useSavedStations - -7. **E2E Tests (Optional but Recommended)** - - File: `frontend/cypress/e2e/stations.cy.ts` (or similar framework) - - Test flows: - - Login → Navigate to Stations - - Search for nearby stations - - Save a station - - View saved stations - - Delete a station - - Navigate to Fuel Logs - - Select station from picker - - Submit fuel log - - Verify station name appears in log - - Use real backend in test environment - - Seed test data if needed - -8. **Run All Tests** - - Backend: `cd backend && npm test` - - Frontend: `cd frontend && npm test` - - Verify 100% pass rate - - Fix any failures - -9. **Backend Documentation** - - File: `backend/src/features/stations/docs/ARCHITECTURE.md` - - System design overview - - Data flow diagrams (text-based) - - External dependencies (Google Maps Places API) - - Caching strategy (Redis + PostgreSQL) - - Database schema with relationships - - Circuit breaker pattern explanation - - File: `backend/src/features/stations/docs/API.md` - - All endpoint documentation - - Request/response examples with curl commands - - Authentication requirements (JWT) - - Rate limits and quotas - - Error response formats - - File: `backend/src/features/stations/docs/TESTING.md` - - How to run tests - - Test database setup - - Writing new tests - - Coverage goals (>80%) - - CI/CD integration - - File: `backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md` - - Google Cloud Console setup - - Create project and enable Places API - - API key creation and restrictions - - Quota management and monitoring - - Cost estimation ($10-50/month typical) - - Security best practices (restrict by IP, referrer) - -10. **Frontend Documentation** - - File: `frontend/src/features/stations/README.md` - - Feature overview - - Component hierarchy diagram (text-based) - - Hook usage examples - - Adding new functionality guide - - Runtime config pattern explanation - - Testing guide - - Integration with other features - - Update: `frontend/docs/RUNTIME-CONFIG.md` (created by Agent 1) - - Ensure complete and accurate - -11. **Update Main Documentation** - - File: `docs/README.md` - - Add "Gas Stations" to features list - - Brief description: "Search and save gas stations using Google Maps" - - Link to backend docs: `backend/src/features/stations/docs/` - - Link to frontend docs: `frontend/src/features/stations/README.md` - - File: `backend/src/features/stations/README.md` - - Update with complete feature overview - - Configuration requirements - - API endpoints summary - - Testing instructions - - Deployment notes - -12. **Create User Guide (Optional)** - - File: `docs/USER-GUIDE-STATIONS.md` - - How to search for stations - - How to save favorites - - How to use in fuel logs - - Screenshots (if possible) - - Troubleshooting common issues - -### Success Criteria -- All backend tests pass (100%) -- All frontend tests pass (100%) -- Test coverage >80% for new code -- All documentation files created -- Documentation is clear and accurate -- Code examples in docs work -- Main README updated -- No TypeScript errors -- Linters pass - -### Handoff Notes -Testing and documentation complete. Feature is production-ready. All code is tested, documented, and follows project standards. - ---- - -## Final Validation Checklist - -After all agents complete, run final validation: - -### Build & Deploy -```bash -make rebuild # Rebuild all containers -make logs # Watch for errors -make migrate # Run database migrations -make test # Run all tests -``` - -### Linting -```bash -cd backend && npm run lint && npm run type-check -cd frontend && npm run lint && npm run type-check -``` - -### Manual Testing -- [ ] Desktop: Search stations at https://motovaultpro.com/stations -- [ ] Desktop: Save a station -- [ ] Desktop: View saved stations -- [ ] Desktop: Delete a station -- [ ] Desktop: Map visualization works -- [ ] Mobile: Navigate to stations (/m/stations) -- [ ] Mobile: All 3 tabs work -- [ ] Mobile: Touch targets are 44px minimum -- [ ] Fuel Logs: Station picker works -- [ ] Fuel Logs: Submit log with station -- [ ] Cross-browser: Chrome, Safari, Firefox - -### Performance -- [ ] Map loads in <2 seconds -- [ ] API responses <500ms -- [ ] Redis caching works (check logs) -- [ ] No memory leaks (React DevTools) - -### Security -- [ ] All endpoints require JWT auth -- [ ] User data isolation works (can't see other users' saved stations) -- [ ] SQL injection protected (prepared statements) -- [ ] API key not exposed to client (runtime config) - -### Documentation -- [ ] All docs created and accurate -- [ ] Code examples work -- [ ] Main README updated -- [ ] Google Maps setup guide complete - -### Standards (per CLAUDE.md) -- [ ] All linters pass with zero issues -- [ ] All tests pass -- [ ] Feature works end-to-end on desktop -- [ ] Feature works end-to-end on mobile -- [ ] Old code deleted (no placeholder routes) -- [ ] No emojis in code or docs -- [ ] Meaningful variable names used - ---- - -## Execution Commands - -To execute agents in parallel using Claude Code: - -### Phase 1 (Parallel) -```bash -# Terminal 1 -claude-code "Execute Agent 1: Frontend-Secrets-Agent as defined in GAS-STATION-AGENTS.md" - -# Terminal 2 -claude-code "Execute Agent 2: Backend-Reliability-Agent as defined in GAS-STATION-AGENTS.md" -``` - -### Phase 2 -```bash -# Wait for Phase 1 to complete -claude-code "Execute Agent 3: Frontend-Foundation-Agent as defined in GAS-STATION-AGENTS.md" -``` - -### Phase 3 (Parallel) -```bash -# Terminal 1 -claude-code "Execute Agent 4: Components-Agent as defined in GAS-STATION-AGENTS.md" - -# Terminal 2 -claude-code "Execute Agent 5: Map-Visualization-Agent as defined in GAS-STATION-AGENTS.md" -``` - -### Phase 4 (Parallel) -```bash -# Terminal 1 -claude-code "Execute Agent 6: Desktop-Mobile-Agent as defined in GAS-STATION-AGENTS.md" - -# Terminal 2 -claude-code "Execute Agent 7: Integration-Agent as defined in GAS-STATION-AGENTS.md" -``` - -### Phase 5 -```bash -# Wait for Phase 4 to complete -claude-code "Execute Agent 8: Testing-Documentation-Agent as defined in GAS-STATION-AGENTS.md" -``` - ---- - -## Notes - -- Each agent is designed to be autonomous and complete its scope independently -- Agents include clear prerequisites and dependencies -- Follow CLAUDE.md standards: no emojis, meaningful names, delete old code -- All implementations follow K8s-aligned patterns (runtime secrets, not build-time) -- Reference implementation: vehicles feature -- Testing is mandatory (per CLAUDE.md: must be 100% green) -- Mobile + Desktop requirement: all features work on both platforms - ---- - -## Estimated Total Time - -- Phase 1: 8 hours (parallel: 8 hours) -- Phase 2: 4 hours -- Phase 3: 8 hours (parallel: 8 hours) -- Phase 4: 8 hours (parallel: 8 hours) -- Phase 5: 12 hours - -**Total: 40 hours elapsed (with parallelization)** -**Total: 48 hours of work (across all agents)** - -**Savings from parallelization: 8 hours (17% faster)** diff --git a/docs/GAS-STATIONS-TESTING-REPORT.md b/docs/GAS-STATIONS-TESTING-REPORT.md deleted file mode 100644 index 6637961..0000000 --- a/docs/GAS-STATIONS-TESTING-REPORT.md +++ /dev/null @@ -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 diff --git a/docs/GAS-STATIONS.md b/docs/GAS-STATIONS.md deleted file mode 100644 index 869c2c5..0000000 --- a/docs/GAS-STATIONS.md +++ /dev/null @@ -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 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 - - saveStation(placeId: string, data: SaveStationData): Promise - - getSavedStations(): Promise - - deleteSavedStation(placeId: string): Promise - - 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 - - requestLocationPermission(): Promise - - 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: Stations (TODO)} /> - - Add: } /> - - 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: } /> - - 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 \ No newline at end of file diff --git a/docs/MAINTENANCE-FEATURE-PLAN.md b/docs/MAINTENANCE-FEATURE-PLAN.md deleted file mode 100644 index b677f24..0000000 --- a/docs/MAINTENANCE-FEATURE-PLAN.md +++ /dev/null @@ -1,1037 +0,0 @@ -# Maintenance Feature Implementation Plan - -## Project Context - -MotoVaultPro is a hybrid platform using: -- **Architecture**: Modular monolith with feature capsules -- **Backend**: Fastify + PostgreSQL + Redis -- **Frontend**: React + TypeScript + Material-UI -- **Development**: Docker-first, production-only testing -- **Requirements**: Mobile + Desktop support for ALL features - -## Feature Overview - -Implement comprehensive maintenance tracking with three main categories: -1. **Routine Maintenance** - Regular service items (27 subtypes) -2. **Repair** - Fix/repair work (5 subtypes) -3. **Performance Upgrade** - Enhancements (5 subtypes) - -### Key Capabilities -- Track completed maintenance (records) -- Schedule recurring maintenance (schedules) -- Calculate next due dates (date-based and/or mileage-based) -- View upcoming/overdue maintenance -- Support multiple subtypes per record (checkboxes, not single select) - -### Display Format -- **List View**: "Category (count)" e.g., "Routine Maintenance (3)" -- **Detail View**: Show all selected subtypes with full details - -## Database Schema - -### Tables to Create - -Drop existing maintenance tables (001_create_maintenance_tables.sql) and create new schema. - -**Migration: `backend/src/features/maintenance/migrations/002_recreate_maintenance_tables.sql`** - -```sql --- Drop existing tables (clean slate) -DROP TABLE IF EXISTS maintenance_schedules CASCADE; -DROP TABLE IF EXISTS maintenance_logs CASCADE; - --- Create maintenance_records table -CREATE TABLE maintenance_records ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id VARCHAR(255) NOT NULL, - vehicle_id UUID NOT NULL, - category VARCHAR(50) NOT NULL, -- 'routine_maintenance', 'repair', 'performance_upgrade' - subtypes TEXT[] NOT NULL, -- PostgreSQL array of selected subtypes - date DATE NOT NULL, - odometer_reading INTEGER, - cost DECIMAL(10, 2), - shop_name VARCHAR(200), - notes TEXT, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT fk_maintenance_vehicle - FOREIGN KEY (vehicle_id) - REFERENCES vehicles(id) - ON DELETE CASCADE, - - CONSTRAINT check_category - CHECK (category IN ('routine_maintenance', 'repair', 'performance_upgrade')), - - CONSTRAINT check_subtypes_not_empty - CHECK (array_length(subtypes, 1) > 0) -); - --- Create maintenance_schedules table -CREATE TABLE maintenance_schedules ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id VARCHAR(255) NOT NULL, - vehicle_id UUID NOT NULL, - category VARCHAR(50) NOT NULL, - subtypes TEXT[] NOT NULL, - interval_months INTEGER, - interval_miles INTEGER, - last_service_date DATE, - last_service_mileage INTEGER, - next_due_date DATE, - next_due_mileage INTEGER, - is_active BOOLEAN DEFAULT true, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT fk_schedule_vehicle - FOREIGN KEY (vehicle_id) - REFERENCES vehicles(id) - ON DELETE CASCADE, - - CONSTRAINT check_schedule_category - CHECK (category IN ('routine_maintenance', 'repair', 'performance_upgrade')) -); - --- Indexes for performance -CREATE INDEX idx_maintenance_records_user_id ON maintenance_records(user_id); -CREATE INDEX idx_maintenance_records_vehicle_id ON maintenance_records(vehicle_id); -CREATE INDEX idx_maintenance_records_date ON maintenance_records(date DESC); -CREATE INDEX idx_maintenance_records_category ON maintenance_records(category); - -CREATE INDEX idx_maintenance_schedules_user_id ON maintenance_schedules(user_id); -CREATE INDEX idx_maintenance_schedules_vehicle_id ON maintenance_schedules(vehicle_id); -CREATE INDEX idx_maintenance_schedules_next_due_date ON maintenance_schedules(next_due_date); -CREATE INDEX idx_maintenance_schedules_active ON maintenance_schedules(is_active) WHERE is_active = true; - --- Triggers for updated_at -DROP TRIGGER IF EXISTS update_maintenance_records_updated_at ON maintenance_records; -CREATE TRIGGER update_maintenance_records_updated_at - BEFORE UPDATE ON maintenance_records - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); - -DROP TRIGGER IF EXISTS update_maintenance_schedules_updated_at ON maintenance_schedules; -CREATE TRIGGER update_maintenance_schedules_updated_at - BEFORE UPDATE ON maintenance_schedules - FOR EACH ROW - EXECUTE FUNCTION update_updated_at_column(); -``` - -## Category and Subtype Definitions - -### Routine Maintenance (27 subtypes) -``` -Accelerator Pedal -Air Filter Element -Brakes and Traction Control -Cabin Air Filter / Purifier -Coolant -Doors -Drive Belt -Engine Oil -Evaporative Emissions System -Exhaust System -Fluid - A/T -Fluid - Differential -Fluid - M/T -Fluid Filter - A/T -Fluids -Fuel Delivery and Air Induction -Hood Shock / Support -Neutral Safety Switch -Parking Brake System -Restraints and Safety Systems -Shift Interlock, A/T -Spark Plug -Steering and Suspension -Tires -Trunk / Liftgate Shock / Support -Washer Fluid -Wiper Blade -``` - -### Repair (5 subtypes) -``` -Engine -Transmission -Drivetrain -Exterior -Interior -``` - -### Performance Upgrade (5 subtypes) -``` -Engine -Drivetrain -Suspension -Wheels/Tires -Exterior -``` - -## Backend Implementation - -### File Structure -``` -backend/src/features/maintenance/ -├── README.md # Feature documentation -├── index.ts # Public API exports -├── api/ -│ ├── maintenance.routes.ts # Fastify routes -│ ├── maintenance.controller.ts # HTTP handlers -│ └── maintenance.validation.ts # Request validation -├── domain/ -│ ├── maintenance.types.ts # TypeScript types + constants -│ └── maintenance.service.ts # Business logic -├── data/ -│ └── maintenance.repository.ts # Database queries -├── migrations/ -│ └── 002_recreate_maintenance_tables.sql -└── tests/ - ├── unit/ - │ └── maintenance.service.test.ts - └── integration/ - └── maintenance.integration.test.ts -``` - -### Phase 1: Domain Layer - -**File: `backend/src/features/maintenance/domain/maintenance.types.ts`** - -```typescript -/** - * @ai-summary Type definitions for maintenance feature - * @ai-context Supports three categories with specific subtypes, multiple selections allowed - */ - -// Category types -export type MaintenanceCategory = 'routine_maintenance' | 'repair' | 'performance_upgrade'; - -// Subtype definitions (constants for validation) -export const ROUTINE_MAINTENANCE_SUBTYPES = [ - 'Accelerator Pedal', - 'Air Filter Element', - 'Brakes and Traction Control', - 'Cabin Air Filter / Purifier', - 'Coolant', - 'Doors', - 'Drive Belt', - 'Engine Oil', - 'Evaporative Emissions System', - 'Exhaust System', - 'Fluid - A/T', - 'Fluid - Differential', - 'Fluid - M/T', - 'Fluid Filter - A/T', - 'Fluids', - 'Fuel Delivery and Air Induction', - 'Hood Shock / Support', - 'Neutral Safety Switch', - 'Parking Brake System', - 'Restraints and Safety Systems', - 'Shift Interlock, A/T', - 'Spark Plug', - 'Steering and Suspension', - 'Tires', - 'Trunk / Liftgate Shock / Support', - 'Washer Fluid', - 'Wiper Blade' -] as const; - -export const REPAIR_SUBTYPES = [ - 'Engine', - 'Transmission', - 'Drivetrain', - 'Exterior', - 'Interior' -] as const; - -export const PERFORMANCE_UPGRADE_SUBTYPES = [ - 'Engine', - 'Drivetrain', - 'Suspension', - 'Wheels/Tires', - 'Exterior' -] as const; - -// Database record types -export interface MaintenanceRecord { - id: string; - user_id: string; - vehicle_id: string; - category: MaintenanceCategory; - subtypes: string[]; - date: string; - odometer_reading?: number; - cost?: number; - shop_name?: string; - notes?: string; - created_at: string; - updated_at: string; -} - -export interface MaintenanceSchedule { - id: string; - user_id: string; - vehicle_id: string; - category: MaintenanceCategory; - subtypes: string[]; - interval_months?: number; - interval_miles?: number; - last_service_date?: string; - last_service_mileage?: number; - next_due_date?: string; - next_due_mileage?: number; - is_active: boolean; - created_at: string; - updated_at: string; -} - -// Request types -export interface CreateMaintenanceRecordRequest { - vehicle_id: string; - category: MaintenanceCategory; - subtypes: string[]; // Must have at least one - date: string; - odometer_reading?: number; - cost?: number; - shop_name?: string; - notes?: string; -} - -export interface UpdateMaintenanceRecordRequest { - category?: MaintenanceCategory; - subtypes?: string[]; - date?: string; - odometer_reading?: number; - cost?: number; - shop_name?: string; - notes?: string; -} - -export interface CreateScheduleRequest { - vehicle_id: string; - category: MaintenanceCategory; - subtypes: string[]; - interval_months?: number; - interval_miles?: number; -} - -export interface UpdateScheduleRequest { - category?: MaintenanceCategory; - subtypes?: string[]; - interval_months?: number; - interval_miles?: number; - is_active?: boolean; -} - -// Response types -export interface MaintenanceRecordResponse extends MaintenanceRecord { - subtype_count: number; // For list display: "Routine Maintenance (3)" -} - -export interface MaintenanceScheduleResponse extends MaintenanceSchedule { - subtype_count: number; - is_due_soon?: boolean; // Within 30 days or 500 miles - is_overdue?: boolean; -} - -// Validation helpers -export function getSubtypesForCategory(category: MaintenanceCategory): readonly string[] { - switch (category) { - case 'routine_maintenance': return ROUTINE_MAINTENANCE_SUBTYPES; - case 'repair': return REPAIR_SUBTYPES; - case 'performance_upgrade': return PERFORMANCE_UPGRADE_SUBTYPES; - } -} - -export function validateSubtypes(category: MaintenanceCategory, subtypes: string[]): boolean { - if (!subtypes || subtypes.length === 0) return false; - const validSubtypes = getSubtypesForCategory(category); - return subtypes.every(st => validSubtypes.includes(st as any)); -} - -export function getCategoryDisplayName(category: MaintenanceCategory): string { - switch (category) { - case 'routine_maintenance': return 'Routine Maintenance'; - case 'repair': return 'Repair'; - case 'performance_upgrade': return 'Performance Upgrade'; - } -} -``` - -**File: `backend/src/features/maintenance/domain/maintenance.service.ts`** - -Key methods to implement: -- `createRecord(data, userId)` - Validate vehicle ownership, validate subtypes match category, create record -- `getRecords(userId, filters?)` - Get user's records, apply filters (vehicleId, category, dateRange) -- `getRecord(id, userId)` - Get single record with ownership check -- `updateRecord(id, data, userId)` - Update with validation -- `deleteRecord(id, userId)` - Soft delete or hard delete -- `getRecordsByVehicle(vehicleId, userId)` - Vehicle-specific records - -- `createSchedule(data, userId)` - Create recurring schedule, calculate initial next_due -- `getSchedules(userId, filters?)` - Get schedules with filters -- `updateSchedule(id, data, userId)` - Update schedule, recalculate next_due if intervals change -- `deleteSchedule(id, userId)` - Remove schedule -- `getUpcomingMaintenance(vehicleId, userId)` - Get schedules that are due soon or overdue - -- `calculateNextDue(schedule, currentDate, currentMileage)` - Calculate next due date/mileage based on intervals - -**Cache Strategy:** -- Records: `maintenance:records:user:{userId}` - 5 min TTL -- Vehicle records: `maintenance:records:vehicle:{vehicleId}` - 5 min TTL -- Schedules: `maintenance:schedules:vehicle:{vehicleId}` - 5 min TTL -- Upcoming: `maintenance:upcoming:{vehicleId}` - 1 hour TTL - -### Phase 2: Data Layer - -**File: `backend/src/features/maintenance/data/maintenance.repository.ts`** - -Key methods (all use prepared statements, all filter by user_id): -- `insert(record)` - INSERT with PostgreSQL array for subtypes -- `findById(id, userId)` - SELECT with user_id check -- `findByUserId(userId)` - SELECT user's records -- `findByVehicleId(vehicleId, userId)` - SELECT vehicle records with ownership check -- `update(id, userId, data)` - UPDATE with user_id check -- `delete(id, userId)` - DELETE with user_id check - -- `insertSchedule(schedule)` - INSERT schedule -- `findSchedulesByVehicle(vehicleId, userId)` - SELECT vehicle schedules -- `updateSchedule(id, userId, data)` - UPDATE schedule -- `deleteSchedule(id, userId)` - DELETE schedule -- `findDueSchedules(vehicleId, userId, currentDate, currentMileage)` - Complex query for due/overdue - -**PostgreSQL Array Handling:** -```typescript -// Insert with array -await pool.query( - 'INSERT INTO maintenance_records (subtypes, ...) VALUES ($1, ...)', - [[subtype1, subtype2, subtype3], ...] -); - -// Query with array contains -await pool.query( - 'SELECT * FROM maintenance_records WHERE $1 = ANY(subtypes)', - [searchSubtype] -); -``` - -### Phase 3: API Layer - -**File: `backend/src/features/maintenance/api/maintenance.routes.ts`** - -```typescript -import { FastifyInstance } from 'fastify'; -import { MaintenanceController } from './maintenance.controller'; - -export async function maintenanceRoutes(app: FastifyInstance) { - const controller = new MaintenanceController(); - - // All routes require authentication - app.addHook('preHandler', app.authenticate); - - // Maintenance Records - app.post('/maintenance/records', controller.createRecord.bind(controller)); - app.get('/maintenance/records', controller.listRecords.bind(controller)); - app.get('/maintenance/records/:id', controller.getRecord.bind(controller)); - app.put('/maintenance/records/:id', controller.updateRecord.bind(controller)); - app.delete('/maintenance/records/:id', controller.deleteRecord.bind(controller)); - app.get('/maintenance/records/vehicle/:vehicleId', controller.getRecordsByVehicle.bind(controller)); - - // Maintenance Schedules - app.post('/maintenance/schedules', controller.createSchedule.bind(controller)); - app.get('/maintenance/schedules', controller.listSchedules.bind(controller)); - app.get('/maintenance/schedules/vehicle/:vehicleId', controller.getSchedulesByVehicle.bind(controller)); - app.put('/maintenance/schedules/:id', controller.updateSchedule.bind(controller)); - app.delete('/maintenance/schedules/:id', controller.deleteSchedule.bind(controller)); - - // Utility endpoints - app.get('/maintenance/upcoming/:vehicleId', controller.getUpcomingMaintenance.bind(controller)); - app.get('/maintenance/subtypes/:category', controller.getSubtypes.bind(controller)); -} -``` - -**File: `backend/src/features/maintenance/api/maintenance.controller.ts`** - -Follow pattern from `backend/src/features/documents/api/documents.controller.ts`: -- Extract userId from `request.user.sub` -- Use structured logging with logger -- Return proper HTTP status codes (201 for create, 200 for success, 404 for not found, etc.) -- Handle errors gracefully - -**File: `backend/src/features/maintenance/api/maintenance.validation.ts`** - -Use validation schemas (Fastify schema or Zod): -- Validate category is valid enum -- Validate subtypes is non-empty array -- Validate subtypes match category (server-side validation) -- Validate dates, numeric values -- Validate UUIDs - -**File: `backend/src/features/maintenance/index.ts`** - -```typescript -export { maintenanceRoutes } from './api/maintenance.routes'; -export * from './domain/maintenance.types'; -``` - -**File: `backend/src/app.ts`** - -Update to register routes (remove lines 118-134 placeholder): - -```typescript -import { maintenanceRoutes } from './features/maintenance'; - -// ... in buildApp() -await app.register(maintenanceRoutes, { prefix: '/api' }); -``` - -### Phase 4: Testing - -**File: `backend/src/features/maintenance/tests/unit/maintenance.service.test.ts`** - -Test cases: -- Create record with valid data -- Reject invalid category -- Reject invalid subtypes for category -- Reject empty subtypes array -- Calculate next due date correctly -- Identify due soon vs overdue -- Handle edge cases (no previous service, etc.) - -**File: `backend/src/features/maintenance/tests/integration/maintenance.integration.test.ts`** - -Test full API workflow: -- Create, read, update, delete records -- Create and manage schedules -- Get upcoming maintenance -- Test authentication (reject without token) -- Test authorization (reject access to other user's data) -- Test PostgreSQL array operations - -## Frontend Implementation - -### File Structure -``` -frontend/src/features/maintenance/ -├── types/ -│ └── maintenance.types.ts # Mirror backend types -├── api/ -│ └── maintenance.api.ts # API client -├── hooks/ -│ ├── useMaintenanceRecords.ts # Records query/mutation -│ ├── useMaintenanceSchedules.ts # Schedules query/mutation -│ └── useUpcomingMaintenance.ts # Upcoming items -├── components/ # Desktop components -│ ├── MaintenanceRecordForm.tsx -│ ├── MaintenanceRecordsList.tsx -│ ├── MaintenanceRecordDetail.tsx -│ ├── MaintenanceScheduleForm.tsx -│ ├── MaintenanceSchedulesList.tsx -│ ├── UpcomingMaintenanceCard.tsx -│ └── SubtypeCheckboxGroup.tsx # Reusable checkbox component -├── mobile/ # Mobile components -│ └── MaintenanceMobileScreen.tsx -└── pages/ - └── MaintenancePage.tsx # Desktop page -``` - -### Phase 5: Frontend Types and API - -**File: `frontend/src/features/maintenance/types/maintenance.types.ts`** - -Copy types from backend, export constants for dropdowns. - -**File: `frontend/src/features/maintenance/api/maintenance.api.ts`** - -```typescript -import { api } from '../../../core/api/client'; - -export const maintenanceApi = { - // Records - createRecord: (data: CreateMaintenanceRecordRequest) => - api.post('/api/maintenance/records', data), - - getRecords: () => - api.get('/api/maintenance/records'), - - getRecord: (id: string) => - api.get(`/api/maintenance/records/${id}`), - - updateRecord: (id: string, data: UpdateMaintenanceRecordRequest) => - api.put(`/api/maintenance/records/${id}`, data), - - deleteRecord: (id: string) => - api.delete(`/api/maintenance/records/${id}`), - - getRecordsByVehicle: (vehicleId: string) => - api.get(`/api/maintenance/records/vehicle/${vehicleId}`), - - // Schedules - createSchedule: (data: CreateScheduleRequest) => - api.post('/api/maintenance/schedules', data), - - getSchedules: () => - api.get('/api/maintenance/schedules'), - - getSchedulesByVehicle: (vehicleId: string) => - api.get(`/api/maintenance/schedules/vehicle/${vehicleId}`), - - updateSchedule: (id: string, data: UpdateScheduleRequest) => - api.put(`/api/maintenance/schedules/${id}`, data), - - deleteSchedule: (id: string) => - api.delete(`/api/maintenance/schedules/${id}`), - - // Utility - getUpcoming: (vehicleId: string) => - api.get(`/api/maintenance/upcoming/${vehicleId}`), - - getSubtypes: (category: MaintenanceCategory) => - api.get(`/api/maintenance/subtypes/${category}`) -}; -``` - -### Phase 6: React Hooks - -**File: `frontend/src/features/maintenance/hooks/useMaintenanceRecords.ts`** - -Use React Query pattern from fuel-logs: - -```typescript -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { maintenanceApi } from '../api/maintenance.api'; - -export function useMaintenanceRecords(vehicleId?: string) { - const queryClient = useQueryClient(); - - const { data: records, isLoading, error } = useQuery({ - queryKey: vehicleId ? ['maintenance-records', vehicleId] : ['maintenance-records'], - queryFn: () => vehicleId - ? maintenanceApi.getRecordsByVehicle(vehicleId) - : maintenanceApi.getRecords() - }); - - const createMutation = useMutation({ - mutationFn: maintenanceApi.createRecord, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['maintenance-records'] }); - } - }); - - const updateMutation = useMutation({ - mutationFn: ({ id, data }: { id: string; data: UpdateMaintenanceRecordRequest }) => - maintenanceApi.updateRecord(id, data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['maintenance-records'] }); - } - }); - - const deleteMutation = useMutation({ - mutationFn: maintenanceApi.deleteRecord, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['maintenance-records'] }); - } - }); - - return { - records, - isLoading, - error, - createRecord: createMutation.mutate, - updateRecord: updateMutation.mutate, - deleteRecord: deleteMutation.mutate - }; -} -``` - -Similar hooks for schedules and upcoming maintenance. - -### Phase 7: Desktop Components - -**File: `frontend/src/features/maintenance/components/SubtypeCheckboxGroup.tsx`** - -Reusable component for subtype selection: - -```typescript -interface SubtypeCheckboxGroupProps { - category: MaintenanceCategory; - selectedSubtypes: string[]; - onChange: (subtypes: string[]) => void; -} - -export function SubtypeCheckboxGroup({ category, selectedSubtypes, onChange }: SubtypeCheckboxGroupProps) { - const subtypes = getSubtypesForCategory(category); - - const handleToggle = (subtype: string) => { - if (selectedSubtypes.includes(subtype)) { - onChange(selectedSubtypes.filter(s => s !== subtype)); - } else { - onChange([...selectedSubtypes, subtype]); - } - }; - - return ( -
- {subtypes.map(subtype => ( - handleToggle(subtype)} - /> - } - label={subtype} - /> - ))} -
- ); -} -``` - -**File: `frontend/src/features/maintenance/components/MaintenanceRecordForm.tsx`** - -Form structure: -1. Category dropdown (Routine Maintenance, Repair, Performance Upgrade) -2. SubtypeCheckboxGroup (dynamically shows based on category) -3. Date picker -4. Odometer input (optional) -5. Cost input (optional) -6. Shop name input (optional) -7. Notes textarea (optional) -8. Submit and Cancel buttons - -Validation: -- Category required -- At least one subtype required -- Selected subtypes must match category -- Date required -- Show error messages inline - -**File: `frontend/src/features/maintenance/components/MaintenanceRecordsList.tsx`** - -Table or card list showing: -- Date (sortable, default newest first) -- Category with count: "Routine Maintenance (3)" -- Odometer reading -- Cost -- Shop name -- Actions (view details, edit, delete) - -Click row to navigate to detail view. - -**File: `frontend/src/features/maintenance/components/MaintenanceRecordDetail.tsx`** - -Full detail view: -- All fields displayed -- Subtypes shown as chips/badges -- Edit and Delete buttons -- Back button - -**File: `frontend/src/features/maintenance/pages/MaintenancePage.tsx`** - -Tabbed interface: -- **Records Tab**: List of completed maintenance -- **Schedules Tab**: Recurring maintenance schedules -- **Upcoming Tab**: Due soon and overdue items - -Include: -- Vehicle selector (dropdown) -- Add new record/schedule buttons -- Filters (category, date range) - -### Phase 8: Mobile Components - -**File: `frontend/src/features/maintenance/mobile/MaintenanceMobileScreen.tsx`** - -Mobile-optimized design: -- GlassCard components (match existing pattern from documents/fuel-logs) -- Touch-friendly form inputs -- Large checkboxes (min 44x44px touch target) -- Single column layout -- Bottom sheet or full-screen modal for add/edit forms -- Swipe actions for delete -- Pull to refresh - -Collapsible sections: -- Tap category to expand/collapse subtype checkboxes -- Accordion style to save vertical space - -**Mobile-specific considerations:** -- Virtual scrolling for long lists -- Optimistic updates for instant feedback -- Loading skeletons -- Error boundaries - -### Phase 9: Route Integration - -**File: `frontend/src/App.tsx`** - -Desktop routes (update line 554): -```typescript -import { lazy } from 'react'; -const MaintenancePage = lazy(() => import('./features/maintenance/pages/MaintenancePage').then(m => ({ default: m.MaintenancePage }))); - -// In Routes: -} /> -``` - -Mobile navigation: -- Maintenance already in Layout.tsx navigation (line 42) -- Consider if it should be in bottom nav or remain in hamburger menu -- If adding to bottom nav, update mobile nav items in App.tsx - -## Display Format Guidelines - -### List View (Records) -``` -┌─────────────────────────────────────────┐ -│ Jan 15, 2024 │ -│ Routine Maintenance (3) │ -│ 45,230 miles | $127.50 | Joe's Auto │ -└─────────────────────────────────────────┘ -``` - -### Detail View (Full Record) -``` -Date: January 15, 2024 -Category: Routine Maintenance -Subtypes: - • Engine Oil - • Air Filter Element - • Cabin Air Filter / Purifier -Odometer: 45,230 miles -Cost: $127.50 -Shop: Joe's Auto Service -Notes: Used synthetic 5W-30 oil. Recommended tire rotation at next visit. -``` - -### Upcoming Maintenance (Color-Coded) -``` -🟢 Good - Not due yet -🟡 Due Soon - Within 30 days or 500 miles -🔴 Overdue - Past due date or mileage -``` - -## Business Rules - -### Validation Rules -1. Category must be one of: routine_maintenance, repair, performance_upgrade -2. Subtypes must be non-empty array -3. All subtypes must be valid for the selected category -4. Date required for records -5. Vehicle must belong to user (ownership check) -6. Interval (months OR miles OR both) required for schedules - -### Next Due Calculation (Schedules) -``` -If interval_months AND interval_miles both set: - next_due_date = last_service_date + interval_months - next_due_mileage = last_service_mileage + interval_miles - Due when EITHER condition is met (whichever comes first) - -If only interval_months: - next_due_date = last_service_date + interval_months - next_due_mileage = null - -If only interval_miles: - next_due_date = null - next_due_mileage = last_service_mileage + interval_miles -``` - -### Due Soon Logic -``` -Due Soon (Yellow): -- next_due_date within 30 days of today -- OR next_due_mileage within 500 miles of current odometer - -Overdue (Red): -- next_due_date in the past -- OR next_due_mileage < current odometer -``` - -## Security Requirements - -1. **All queries user-scoped**: Every database query MUST filter by user_id -2. **Vehicle ownership**: Validate user owns vehicle before any operation -3. **Prepared statements**: NEVER concatenate SQL strings -4. **Authentication**: All routes require valid JWT token -5. **Authorization**: Users can only access their own data - -Example repository pattern: -```typescript -async findByVehicleId(vehicleId: string, userId: string): Promise { - // CORRECT - filters by both vehicle_id AND user_id - const result = await pool.query( - 'SELECT * FROM maintenance_records WHERE vehicle_id = $1 AND user_id = $2', - [vehicleId, userId] - ); - return result.rows; -} -``` - -## Caching Strategy - -Use Redis for caching: - -```typescript -// Records cache - 5 minutes -const cacheKey = `maintenance:records:user:${userId}`; -const ttl = 300; // 5 minutes - -// Vehicle-specific cache - 5 minutes -const vehicleCacheKey = `maintenance:records:vehicle:${vehicleId}`; - -// Upcoming maintenance - 1 hour (less frequently changing) -const upcomingCacheKey = `maintenance:upcoming:${vehicleId}`; -const upcomingTTL = 3600; // 1 hour - -// Invalidate on create/update/delete -await cacheService.del(cacheKey); -await cacheService.del(vehicleCacheKey); -await cacheService.del(upcomingCacheKey); -``` - -## Testing Strategy - -### Backend Tests - -**Unit Tests** (`backend/src/features/maintenance/tests/unit/`) -- Test service methods with mocked repository -- Test validation logic (category, subtypes) -- Test next due calculation -- Test due soon/overdue logic -- Test edge cases (no previous service, missing data) - -**Integration Tests** (`backend/src/features/maintenance/tests/integration/`) -- Test full API endpoints with test database -- Test authentication (401 without token) -- Test authorization (403 for other user's data) -- Test PostgreSQL array operations -- Test cascade deletes (vehicle deletion) - -Run tests: -```bash -make shell-backend -npm test -- features/maintenance -``` - -### Frontend Tests - -Test components: -- Form validation (category, subtypes) -- Checkbox selection/deselection -- Mobile touch interactions -- Responsive layout - -### Manual Testing (Docker-Only) - -1. After each change: `make rebuild` -2. Test mobile viewport: 375px width -3. Test desktop viewport: 1920px width -4. Test touch interactions on mobile -5. Verify all linting hooks pass (zero tolerance) - -## Documentation Requirements - -**File: `backend/src/features/maintenance/README.md`** - -Follow pattern from fuel-logs README: - -```markdown -# Maintenance Feature Capsule - -## Quick Summary (50 tokens) -Tracks vehicle maintenance including routine service, repairs, and performance upgrades. Supports multiple subtypes per record, recurring schedules, and upcoming/overdue calculations. User-scoped data with vehicle ownership enforcement. - -## API Endpoints -[List all endpoints with descriptions] - -## Structure -- **api/** - HTTP endpoints, routes, validators -- **domain/** - Business logic, types, rules -- **data/** - Repository, database queries -- **migrations/** - Feature-specific schema -- **tests/** - All feature tests - -## Categories and Subtypes -[List all three categories and their subtypes] - -## Dependencies -- Internal: core/auth, core/cache, core/config -- Database: maintenance_records, maintenance_schedules tables -- Feature: vehicles (vehicle_id FK) - -## Business Rules -[Document validation, calculation logic, etc.] - -## Testing -[Document test commands and examples] -``` - -## Success Criteria Checklist - -- [ ] Database migrations run cleanly (`make migrate`) -- [ ] Backend compiles without errors -- [ ] All backend unit tests pass -- [ ] All backend integration tests pass -- [ ] All TypeScript types are correct (no `any` types) -- [ ] All linting rules pass (ESLint, Prettier) -- [ ] Category dropdown works correctly -- [ ] Subtype checkboxes populate based on selected category -- [ ] Multiple subtypes can be selected -- [ ] Subtype validation prevents invalid selections -- [ ] Records display as "Category (count)" in list view -- [ ] Detail view shows all selected subtypes -- [ ] Schedule creation works -- [ ] Next due date calculation is correct -- [ ] Upcoming maintenance shows correct items -- [ ] Works on mobile (375px viewport) -- [ ] Touch targets are 44x44px minimum on mobile -- [ ] Works on desktop (1920px viewport) -- [ ] Responsive between mobile and desktop breakpoints -- [ ] No console errors -- [ ] No TODOs remaining in code -- [ ] README.md is complete and accurate -- [ ] Feature is registered in backend app.ts -- [ ] Feature is registered in frontend App.tsx routes - -## Common Pitfalls to Avoid - -1. **PostgreSQL Arrays**: Use proper array syntax `TEXT[]` and array operations `= ANY(subtypes)` -2. **User Scoping**: NEVER forget `AND user_id = $X` in queries -3. **Category Validation**: Server-side validation is required (don't trust client) -4. **Empty Subtypes**: Validate array is non-empty before saving -5. **Mobile Touch Targets**: Checkboxes must be 44x44px minimum -6. **Cache Invalidation**: Invalidate ALL relevant cache keys on update -7. **String Concatenation**: NEVER concatenate SQL strings - use prepared statements -8. **Type Safety**: Don't use `any` types - define proper interfaces - -## Reference Files - -For implementation patterns, refer to these existing features: -- **Documents Feature**: `backend/src/features/documents/` (most recent, best example) -- **Fuel Logs Feature**: `backend/src/features/fuel-logs/` (similar complexity) -- **Documents Frontend**: `frontend/src/features/documents/` (mobile + desktop patterns) - -## Implementation Order - -1. Backend database migration -2. Backend domain types -3. Backend repository -4. Backend service -5. Backend API (routes, controller, validation) -6. Backend tests -7. Register routes in app.ts -8. Frontend types -9. Frontend API client -10. Frontend hooks -11. Desktop components (form, list, detail) -12. Desktop page with tabs -13. Mobile components -14. Update routes in App.tsx -15. Manual testing (docker rebuild) -16. Documentation (README.md) -17. Final validation (all criteria met) diff --git a/docs/PROMPTS.md b/docs/PROMPTS.md index b6466a7..7f05e55 100644 --- a/docs/PROMPTS.md +++ b/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` +*** CRITICAL ***: Never read this file. Stop and move on. \ No newline at end of file diff --git a/docs/TESTING.md b/docs/TESTING.md index 38c1896..262e778 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.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 diff --git a/docs/VEHICLES-API.md b/docs/VEHICLES-API.md index 4a757a6..4e90e7d 100644 --- a/docs/VEHICLES-API.md +++ b/docs/VEHICLES-API.md @@ -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 diff --git a/frontend/src/features/stations/README.md b/frontend/src/features/stations/README.md index 9a37f7c..69d8b22 100644 --- a/frontend/src/features/stations/README.md +++ b/frontend/src/features/stations/README.md @@ -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 diff --git a/frontend/src/features/vehicles/api/vehicles.api.ts b/frontend/src/features/vehicles/api/vehicles.api.ts index de21a6b..99c2634 100644 --- a/frontend/src/features/vehicles/api/vehicles.api.ts +++ b/frontend/src/features/vehicles/api/vehicles.api.ts @@ -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 => { - const response = await apiClient.get('/platform/years'); + const response = await apiClient.get('/vehicles/dropdown/years'); return response.data; }, getMakes: async (year: number): Promise => { - 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 => { - 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 => { - 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 => { - 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 => { - 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; }, diff --git a/frontend/src/features/vehicles/components/VehicleForm.tsx b/frontend/src/features/vehicles/components/VehicleForm.tsx index c7ec709..a14ce80 100644 --- a/frontend/src/features/vehicles/components/VehicleForm.tsx +++ b/frontend/src/features/vehicles/components/VehicleForm.tsx @@ -72,6 +72,7 @@ export const VehicleForm: React.FC = ({ const [models, setModels] = useState([]); const [engines, setEngines] = useState([]); const [trims, setTrims] = useState([]); + const [transmissions, setTransmissions] = useState([]); const [selectedYear, setSelectedYear] = useState(); const [selectedMake, setSelectedMake] = useState(); const [selectedModel, setSelectedModel] = useState(); @@ -155,6 +156,7 @@ export const VehicleForm: React.FC = ({ setModels([]); setEngines([]); setTrims([]); + setTransmissions([]); setSelectedMake(undefined); setSelectedModel(undefined); setValue('make', ''); @@ -189,6 +191,7 @@ export const VehicleForm: React.FC = ({ // Clear dependent selections setEngines([]); setTrims([]); + setTransmissions([]); setSelectedModel(undefined); setValue('model', ''); setValue('transmission', ''); @@ -215,8 +218,12 @@ export const VehicleForm: React.FC = ({ 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 = ({ console.error('Failed to load detailed data:', error); setTrims([]); setEngines([]); + setTransmissions([]); } finally { setLoadingDropdowns(false); } @@ -397,7 +405,7 @@ export const VehicleForm: React.FC = ({ - {/* Transmission (right, static options) */} + {/* Transmission (right) */}