Files
motovaultpro/docs/GAS-STATIONS.md
Eric Gullickson 2cc9cc5f9f Gas Station Prep
2025-11-03 14:18:25 -06:00

19 KiB

Gas Stations Feature - Complete Implementation Plan (K8s-Aligned Secrets)

Overview

Implement complete Gas Stations feature with full integration, map visualization, backend improvements, and comprehensive testing. Uses K8s-aligned runtime secrets pattern for frontend Google Maps API key.

Phase 1: Frontend Secrets Infrastructure (K8s-Aligned)

1.1 Create Frontend Config Loader

Create frontend/scripts/load-config.sh:

  • Bash script that runs as container entrypoint
  • Reads /run/secrets/google-maps-api-key
  • Generates /usr/share/nginx/html/config.js with: window.CONFIG = { googleMapsApiKey: '...' }
  • Falls back to empty string if secret not found
  • Logs success/failure for debugging

1.2 Update Frontend Dockerfile

Modify frontend/Dockerfile:

  • Copy load-config.sh into image
  • Make script executable
  • Add SECRETS_DIR environment variable (default: /run/secrets)
  • Update CMD to run script before nginx: ["sh", "-c", "/app/load-config.sh && nginx -g 'daemon off;'"]

1.3 Update Frontend index.html

Modify frontend/index.html:

  • Add <script src="/config.js"></script> before app bundle
  • Config loads before React app initializes
  • Add TypeScript types for window.CONFIG

1.4 Create Frontend Config Types

Create frontend/src/core/config/config.types.ts:

  • Interface for window.CONFIG
  • Export typed accessor: getConfig()
  • Runtime validation (throw if required values missing)

1.5 Update docker-compose.yml

Add to mvp-frontend service: volumes:

  • ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro environment: SECRETS_DIR: /run/secrets

1.6 Document Pattern

Create frontend/docs/RUNTIME-CONFIG.md:

  • Explain runtime config pattern
  • How to add new runtime secrets
  • K8s deployment notes
  • Local development setup

Phase 2: Backend Improvements

2.1 Add Circuit Breaker Pattern

  • Install opossum package: backend/package.json
  • Create backend/src/features/stations/external/google-maps/google-maps.circuit-breaker.ts
  • Update google-maps.client.ts to wrap API calls
  • Follow platform feature pattern (reference: backend/src/features/platform/)
  • Config: 10s timeout, 50% threshold, 30s reset

2.2 Backend Unit Tests

Create backend/src/features/stations/tests/unit/:

  • stations.service.test.ts - Business logic, user isolation
  • stations.repository.test.ts - Database operations, SQL queries
  • google-maps.client.test.ts - API mocking, distance calculation
  • Use test fixtures for sample data

2.3 Backend Integration Tests

Create backend/src/features/stations/tests/integration/:

  • stations.api.test.ts - Full endpoint testing with real database
  • saved-stations.flow.test.ts - Save, retrieve, delete workflow
  • Use test database (configured in test setup)

2.4 Test Fixtures

Create backend/src/features/stations/tests/fixtures/:

  • mock-stations.ts - Sample station objects
  • mock-google-response.ts - Mock Google Places API responses
  • test-coordinates.ts - Test location data

2.5 Cache Cleanup Job

Create backend/src/features/stations/jobs/cache-cleanup.job.ts:

  • Scheduled job (daily at 2 AM)
  • Delete station_cache entries older than 24 hours
  • Log cleanup metrics (rows deleted)
  • Register in main app scheduler

Phase 3: Frontend Foundation

3.1 Type Definitions

Create frontend/src/features/stations/types/stations.types.ts:

  • Station interface (id, name, address, location, rating, distance, photoUrl, prices)
  • SavedStation interface (extends Station, adds nickname, notes, isFavorite)
  • SearchRequest interface (latitude, longitude, radius?, fuelType?)
  • SearchResponse interface
  • MapMarker interface for map pins

3.2 API Client

Create frontend/src/features/stations/api/stations.api.ts:

  • searchStations(request: SearchRequest): Promise<Station[]>
  • saveStation(placeId: string, data: SaveStationData): Promise
  • getSavedStations(): Promise<SavedStation[]>
  • 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: <Route path="/stations" element={
    Stations (TODO)
    } />
  • Add: <Route path="/stations" element={} />
  • Import: import { StationsPage } from './features/stations/pages/StationsPage'
  • Ensure route is inside ProtectedRoute wrapper

Phase 6: Mobile Implementation

6.1 Mobile Screen

Create frontend/src/features/stations/pages/StationsMobileScreen.tsx:

  • BottomNavigation with 3 tabs: Search, Saved, Map
  • Tab 0 (Search):
    • StationsSearchForm (compact mode)
    • StationsList (vertical scroll)
    • Pull-to-refresh support
  • Tab 1 (Saved):
    • SavedStationsList (full screen)
    • Swipe to delete gestures
  • Tab 2 (Map):
    • StationMap (full screen, 100vh)
    • Floating search button (FAB) to go back to search tab
  • Bottom sheet for station details (SwipeableDrawer)
  • Touch targets: 44px minimum
  • Safe area insets for notched devices

6.2 Mobile Routing

Update frontend/src/App.tsx:

  • Add mobile route: <Route path="/m/stations" element={} />
  • 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