6.8 KiB
6.8 KiB
Gas Stations Feature
Complete gas station discovery and management feature with Google Maps integration, caching, and user favorites.
Quick Summary
Search nearby gas stations via Google Maps API. Users can view stations on a map, save favorites with custom notes, and integrate station data into fuel logging. Search results cached for 1 hour. JWT required for all endpoints. User data isolation via user_id.
Implementation Phases Status
| Phase | Feature | Status |
|---|---|---|
| 1 | Frontend K8s-aligned secrets pattern | ✅ Complete |
| 2 | Backend improvements (circuit breaker, tests) | ✅ Complete |
| 3 | Frontend foundation (types, API, hooks) | ✅ Complete |
| 4 | Frontend components (card, list, form, map) | ✅ Complete |
| 5 | Desktop page with map/search layout | ✅ Complete |
| 6 | Mobile screen with tab navigation | 🔄 In Progress |
| 7 | Fuel logs integration (StationPicker) | ⏳ Pending |
| 8 | Testing (unit, integration, E2E) | ⏳ Pending |
| 9 | Documentation (API, setup, troubleshooting) | ⏳ Pending |
| 10 | Validation & Polish (lint, tests, manual) | ⏳ Pending |
| 11 | Deployment preparation & checklist | ⏳ Pending |
Architecture
Backend Structure
features/stations/
├── api/ # HTTP handlers and routes
│ ├── stations.controller.ts
│ └── stations.routes.ts
├── domain/ # Business logic
│ ├── stations.service.ts
│ └── stations.types.ts
├── data/ # Database access
│ └── stations.repository.ts
├── external/google-maps/ # External API
│ ├── google-maps.client.ts
│ ├── google-maps.circuit-breaker.ts
│ └── google-maps.types.ts
├── jobs/ # Scheduled tasks
│ └── cache-cleanup.job.ts
├── migrations/ # Database schema
│ ├── 001_create_stations_tables.sql
│ └── 002_add_indexes.sql
├── tests/ # Test suite
│ ├── fixtures/
│ ├── unit/
│ └── integration/
└── index.ts # Feature exports
Frontend Structure
features/stations/
├── types/ # TypeScript definitions
│ └── stations.types.ts
├── api/ # API client
│ └── stations.api.ts
├── hooks/ # React Query hooks
│ ├── useStationsSearch.ts
│ ├── useSavedStations.ts
│ ├── useSaveStation.ts
│ ├── useDeleteStation.ts
│ └── useGeolocation.ts
├── utils/ # Utilities
│ ├── distance.ts
│ ├── maps-loader.ts
│ └── map-utils.ts
├── components/ # React components
│ ├── StationCard.tsx
│ ├── StationsList.tsx
│ ├── SavedStationsList.tsx
│ ├── StationsSearchForm.tsx
│ └── StationMap.tsx
├── pages/ # Page layouts
│ └── StationsPage.tsx # Desktop
├── mobile/ # Mobile layouts (Phase 6)
│ └── StationsMobileScreen.tsx
└── __tests__/ # Tests (Phase 8)
API Endpoints (All require JWT)
POST /api/stations/search
Body: { latitude, longitude, radius?, fuelType? }
Response: { stations[], searchLocation, searchRadius, timestamp }
POST /api/stations/save
Body: { placeId, nickname?, notes?, isFavorite? }
Response: SavedStation
GET /api/stations/saved
Response: SavedStation[]
GET /api/stations/saved/:placeId
Response: SavedStation | 404
PATCH /api/stations/saved/:placeId
Body: { nickname?, notes?, isFavorite? }
Response: SavedStation
DELETE /api/stations/saved/:placeId
Response: 204
Database Schema
station_cache
Temporary Google Places results (auto-cleanup after 24h)
- id: UUID PRIMARY KEY
- place_id: VARCHAR UNIQUE
- name: VARCHAR
- address: VARCHAR
- latitude: DECIMAL
- longitude: DECIMAL
- rating: DECIMAL
- photo_url: VARCHAR
- created_at: TIMESTAMP
saved_stations
User's favorite stations with metadata
- id: UUID PRIMARY KEY
- user_id: VARCHAR (indexed)
- place_id: VARCHAR (indexed)
- nickname: VARCHAR
- notes: TEXT
- is_favorite: BOOLEAN
- created_at: TIMESTAMP
- updated_at: TIMESTAMP
- deleted_at: TIMESTAMP (soft delete)
- UNIQUE(user_id, place_id)
Key Features Implemented
✅ Security
- User-scoped data isolation via
user_idfiltering - Parameterized queries (no SQL injection)
- JWT authentication required on all endpoints
- K8s-aligned secrets pattern (never in environment variables)
✅ Performance
- Redis caching with 1-hour TTL
- Circuit breaker for resilience (10s timeout, 50% threshold)
- Database indexes on user_id and place_id
- Scheduled cache cleanup (24h auto-expiry)
- Lazy-loaded Google Maps API in browser
✅ User Experience
- Real-time browser geolocation with permission handling
- Touch-friendly 44px minimum button heights
- Responsive map with auto-fit bounds
- Saved stations with custom nicknames and notes
- One-click directions to Google Maps
Testing
Run Tests
# Backend feature tests
cd backend
npm test -- features/stations
# Frontend component tests (Phase 8)
cd frontend
npm test -- stations
# E2E tests (Phase 8)
npm run e2e
Coverage Goals
- Backend: >80% coverage
- Frontend components: >80% coverage
- All critical paths tested
Deployment
Prerequisites
- Google Maps API key with Places API enabled
- PostgreSQL database with migrations applied
- Redis cache service running
- Docker for containerization
Setup
# Create secrets directory
mkdir -p ./secrets/app
# Add Google Maps API key
echo "YOUR_API_KEY_HERE" > ./secrets/app/google-maps-api-key.txt
# Build and start
make setup
make logs
# Verify
curl http://localhost:3001/health
Verification
# Check secrets mounted
docker compose exec mvp-frontend cat /run/secrets/google-maps-api-key
# Check config generated
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
Next Steps (Phases 6-11)
Phase 6: Mobile Implementation
Create mobile bottom-tab navigation screen with Search, Saved, Map tabs.
Phase 7: Fuel Logs Integration
Add StationPicker component with autocomplete to FuelLogForm.
Phases 8-11: Testing, Docs, Validation, Deployment
Follow the detailed plan in /docs/GAS-STATIONS.md.
References
- Full implementation plan:
/docs/GAS-STATIONS.md - Runtime config pattern:
/frontend/docs/RUNTIME-CONFIG.md - Design patterns: See Platform feature (
backend/src/features/platform/) - Component patterns: See Vehicles feature (
frontend/src/features/vehicles/)