backend/main.py) that runs on port 8000. It manages several long-lived background services and exposes a REST API consumed by the Next.js frontend.
App structure
Lifespan and startup sequence
FastAPI’s@asynccontextmanager lifespan handler orchestrates startup and shutdown:
Data fetcher and scheduler
services/data_fetcher.py is the orchestration hub. It defines two refresh tiers and registers them with APScheduler:
BackgroundScheduler with daemon=True.
| Job | Interval | max_instances |
|---|---|---|
fast_tier | 60s | 1 |
slow_tier | 5 min | 1 |
gdelt | 15 min | 1 |
liveuamap | 15 min | 1 |
| CCTV ingestors (×4) | 10 min | 1 |
Service modules
ais_stream.py
Manages a Node.js subprocess (
ais_proxy.js) that holds the WebSocket connection to aisstream.io. Vessel positions stream in via stdout. Supports disk caching (ais_cache.json) and local AIS-catcher ingestion via POST /api/ais/feed. Vessels are pruned after 15 minutes of inactivity.carrier_tracker.py
Tracks all 11 active US Navy aircraft carriers using GDELT news scraping and 50+ geographic region-to-coordinate mappings. Positions are disk-cached in
carrier_cache.json and updated at 00:00 and 12:00 UTC.cctv_pipeline.py
Ingests live traffic cameras from four sources: Transport for London JamCams, Austin TX TxDOT, NYC DOT, and Singapore LTA. Cameras are stored in a SQLite database (
cctv.db) and refreshed every 10 minutes.geopolitics.py
Fetches GDELT conflict events (last 8 hours) and the Ukraine frontline GeoJSON from DeepState Map. GDELT is updated every 15 minutes; the frontline runs on the slow 5-minute tier.
region_dossier.py
Handles right-click map requests. Reverse-geocodes a lat/lng, then queries RestCountries for a country profile, Wikidata SPARQL for the head of state, and Wikipedia for a local summary and thumbnail. Results are cached for 24 hours.
sentinel_search.py
Queries the Microsoft Planetary Computer STAC API for the most recent Sentinel-2 L2A scene at a given point. Returns capture date, cloud cover, and a thumbnail URL for the right-click intel card.
radio_intercept.py
Integrates with Broadcastify (top scanner feeds) and OpenMHz (trunked radio call archives). Provides nearest-system lookups by lat/lng for the Radio Intercept Panel.
kiwisdr_fetcher.py
Scrapes the KiwiSDR public receiver list (~500+ nodes worldwide). Each node includes name, location, antenna type, frequency bands, and active user count.
Admin authentication
Sensitive endpoints (API key management, news feed config, system update) are protected by anX-Admin-Key header dependency:
ADMIN_KEY in .env or via Docker Swarm secrets for production deployments. If the variable is unset, a warning is logged at startup and endpoints remain open — safe for local development, not for public exposure.
Rate limiting
All endpoints use slowapi withget_remote_address as the key function:
| Endpoint | Limit |
|---|---|
/api/live-data/fast | 120/min |
/api/live-data/slow | 60/min |
/api/radio/* | 30–60/min |
/api/region-dossier | 30/min |
/api/sentinel2/search | 30/min |
/api/refresh | 2/min |
/api/system/update | 1/min |
ETag caching
Both/api/live-data/fast and /api/live-data/slow use a shared _etag_response helper that serializes the payload once, computes an MD5 ETag, and returns 304 Not Modified if the client’s If-None-Match header matches:
s,w,n,e) so viewport-filtered responses are cached separately from full-dataset responses.
Gzip compression
GZipMiddleware is applied at the middleware level with a 1 KB threshold:
Docker Swarm secrets
Before any service modules are imported,main.py checks for *_FILE environment variables and reads the corresponding file contents into the matching environment variable:
os.environ at import time. Mount your secrets at /run/secrets/<name> and set AIS_API_KEY_FILE=/run/secrets/AIS_API_KEY to use them.
Pydantic schemas
Response models live inservices/schemas.py: