mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
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.
This commit is contained in:
@@ -127,7 +127,7 @@ paths:
|
||||
- name: icao24
|
||||
in: query
|
||||
description: ICAO 24-bit hex address (lowercase).
|
||||
required: true
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -240,6 +240,78 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
/api/military/v1/list-military-bases:
|
||||
get:
|
||||
tags:
|
||||
- MilitaryService
|
||||
summary: ListMilitaryBases
|
||||
description: ListMilitaryBases retrieves military bases within a bounding box, with server-side clustering.
|
||||
operationId: ListMilitaryBases
|
||||
parameters:
|
||||
- name: ne_lat
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: number
|
||||
format: double
|
||||
- name: ne_lon
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: number
|
||||
format: double
|
||||
- name: sw_lat
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: number
|
||||
format: double
|
||||
- name: sw_lon
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: number
|
||||
format: double
|
||||
- name: zoom
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
- name: type
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: kind
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: country
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ListMilitaryBasesResponse'
|
||||
"400":
|
||||
description: Validation error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
default:
|
||||
description: Error response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
components:
|
||||
schemas:
|
||||
Error:
|
||||
@@ -873,3 +945,96 @@ components:
|
||||
type: string
|
||||
description: Escort vessels in the strike group.
|
||||
description: USNIStrikeGroup represents a carrier strike group parsed from the article.
|
||||
ListMilitaryBasesRequest:
|
||||
type: object
|
||||
properties:
|
||||
neLat:
|
||||
type: number
|
||||
format: double
|
||||
neLon:
|
||||
type: number
|
||||
format: double
|
||||
swLat:
|
||||
type: number
|
||||
format: double
|
||||
swLon:
|
||||
type: number
|
||||
format: double
|
||||
zoom:
|
||||
type: integer
|
||||
format: int32
|
||||
type:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
ListMilitaryBasesResponse:
|
||||
type: object
|
||||
properties:
|
||||
bases:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MilitaryBaseEntry'
|
||||
clusters:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MilitaryBaseCluster'
|
||||
totalInView:
|
||||
type: integer
|
||||
format: int32
|
||||
truncated:
|
||||
type: boolean
|
||||
MilitaryBaseEntry:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
latitude:
|
||||
type: number
|
||||
format: double
|
||||
longitude:
|
||||
type: number
|
||||
format: double
|
||||
kind:
|
||||
type: string
|
||||
countryIso2:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
tier:
|
||||
type: integer
|
||||
format: int32
|
||||
catAirforce:
|
||||
type: boolean
|
||||
catNaval:
|
||||
type: boolean
|
||||
catNuclear:
|
||||
type: boolean
|
||||
catSpace:
|
||||
type: boolean
|
||||
catTraining:
|
||||
type: boolean
|
||||
branch:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
MilitaryBaseCluster:
|
||||
type: object
|
||||
properties:
|
||||
latitude:
|
||||
type: number
|
||||
format: double
|
||||
longitude:
|
||||
type: number
|
||||
format: double
|
||||
count:
|
||||
type: integer
|
||||
format: int32
|
||||
dominantType:
|
||||
type: string
|
||||
expansionZoom:
|
||||
type: integer
|
||||
format: int32
|
||||
|
||||
Reference in New Issue
Block a user