Commit Graph

1 Commits

Author SHA1 Message Date
Elie Habib
3d2c638a72 feat(military): server-side military bases 125K + rate limiting (#496)
* feat(military): server-side military bases with 125K entries + rate limiting (#485)

Migrate military bases from 224 static client-side entries to 125,380
server-side entries stored in Redis GEO sorted sets, served via
bbox-filtered GEOSEARCH endpoint with server-side clustering.

Data pipeline:
- Pizzint/Polyglobe: 79,156 entries (Supabase extraction)
- OpenStreetMap: 45,185 entries
- MIRTA: 821 entries
- Curated strategic: 218 entries
- 277 proximity duplicates removed

Server:
- ListMilitaryBases RPC with GEOSEARCH + HMGET + tier/filter/clustering
- Antimeridian handling (split bbox queries)
- Blue-green Redis deployment with atomic version pointer switch
- geoSearchByBox() + getHashFieldsBatch() helpers in redis.ts

Security:
- @upstash/ratelimit: 60 req/min sliding window per IP
- IP spoofing fix: prioritize x-real-ip (Vercel-injected) over x-forwarded-for
- Require API key for non-browser requests (blocks unauthenticated curl/scripts)
- Input validation: allowlisted types/kinds, regex country, clamped bbox/zoom

Frontend:
- Viewport-driven loading with bbox quantization + debounce
- Server-side grid clustering at low zoom levels
- Enriched popup with kind, category badges (airforce/naval/nuclear/space)
- Static 224 bases kept as search fallback + initial render

* fix(military): fallback to production Redis keys in preview deployments

Preview deployments prefix Redis keys with `preview:{sha}:` but military
bases data is seeded to unprefixed (production) keys. When the prefixed
`military:bases:active` key is missing, fall back to the unprefixed key
and use raw (unprefixed) keys for geo/meta lookups.

* fix: remove unused 'remaining' destructure in rate-limit (TS6133)

* ci: add typecheck:api to pre-push hook to catch server-side TS errors

* debug(military): add X-Bases-Debug response header for preview diagnostics

* fix(bases): trigger initial server fetch on map load

fetchServerBases() was only called on moveend — if the user
never panned/zoomed, the API was never called and only the 224
static fallback bases showed.
2026-02-28 09:16:59 +04:00