Skip to content

Architecture

A reference for the moving parts. Useful before you self-host, before you contribute, and before you debug anything operational.

High-level shape

                        ┌─────────────────┐
                        │   User browser  │
                        │  React + Cesium │
                        └────────┬────────┘
                                 │  HTTPS, WebSocket

        ┌────────────────────────────────────────────┐
        │   FastAPI backend (uvicorn)                │
        │   ┌──────────────┐  ┌─────────────────┐    │
        │   │ HTTP routes  │  │ WebSocket route │    │
        │   └──────┬───────┘  └────────┬────────┘    │
        │          │                   │              │
        │   ┌──────▼───────────────────▼──────┐       │
        │   │ Services layer (httpx clients)  │       │
        │   └──┬──────┬──────┬───────┬──────┬─┘       │
        └──────┼──────┼──────┼───────┼──────┼─────────┘
               │      │      │       │      │
               ▼      ▼      ▼       ▼      ▼
           Finnhub  EODHD  OpenSky  MT     Stripe
                              MarineTraffic

           ┌─────────────┐   ┌──────────┐
           │ PostgreSQL  │   │  Redis   │
           └─────────────┘   └──────────┘

Components

Frontend — React + Vite (frontend/)

  • Stack: React, Vite, plain JavaScript (no TypeScript), Tailwind, React Router.
  • Entry: frontend/src/App.jsx.
  • Workspace root: OSINTDashboard.jsx — orchestrates all panels.
  • Routing: Two protected routes (/, /dashboard/:tab), three public (/login, /register, /pricing), one admin (/admin).
  • State: Local component state + a small set of context providers; no global Redux/MobX. Server state is fetched per panel via small hooks (e.g., useMarketData.js).
  • i18n: 6 locales in frontend/src/i18n/. All keys mirrored across all locales.
  • 3D globe: Cesium, accessed via the public VITE_CESIUM_ION_TOKEN.
  • Build output: Static SPA — deployable behind any CDN.

Backend — FastAPI (backend/)

  • Stack: Python 3.11+, FastAPI, SQLAlchemy, Alembic, httpx (async), Poetry.
  • Entry: backend/main.py.
  • Routes: Modular under backend/routes/ — one file per surface (auth, market, news, alerts, ai_chat, vessels, aircraft, …).
  • Services: backend/services/ — one file per upstream provider, all httpx.AsyncClient-based with explicit timeouts and asyncio.Semaphore for rate limits.
  • Auth: JWT access tokens (short-lived) + refresh tokens (HTTP-only cookie). 2FA via TOTP.
  • WebSocket: backend/routes/websocket.py for live quote streams and alert delivery.

Database — PostgreSQL

  • Schemas: users, watchlists, alerts, screens, graphs, dashboards, portfolios, subscriptions.
  • Migrations: Alembic — alembic upgrade head after every backend update.
  • Recommended version: 14+. Earlier versions miss features like generated columns used in some indexes.

Cache — Redis

  • What it caches:
    • Short-TTL upstream responses (quotes, OHLC, fundamentals) so the backend doesn't hammer Finnhub / EODHD on every request.
    • Rate-limit counters per user and per upstream provider.
    • WebSocket session state.
  • Recommended version: 6+.
  • Atlas does not run without Redis — the rate-limit middleware refuses to start if REDIS_URL doesn't connect.

Documentation — VitePress (apps/docs/)

  • Independent app: static deploy, separate from frontend and backend.
  • Glossary auto-gen: generate-glossary-docs.mjs reads frontend/src/components/onboarding/glossary.js and writes a single auto-managed page.
  • Screenshot capture: capture-screenshots.mjs drives Playwright against the running frontend.

Request lifecycle

For a typical "give me AAPL quote" request:

  1. FrontenduseMarketData('AAPL') fires.
  2. HTTPGET /api/market/quote?symbol=AAPL with the access-token cookie.
  3. Backend routebackend/routes/market.py authenticates, rate-limits via Redis, then calls the service layer.
  4. Servicefinnhub.py checks Redis cache; on miss, calls Finnhub with httpx.AsyncClient, populates cache, returns.
  5. Response — JSON to the frontend.
  6. Render — Market panel displays the quote with a realtime/delayed badge based on the response timestamp.

For a live stream, the WebSocket channel replaces step 2–5 with a long-lived subscription, and the service layer publishes ticks as they arrive.

Secrets boundary

A non-negotiable rule: the frontend never has a backend-provider key. The split:

VariableSidePublic?
EODHD_API_KEYBackendNo
OPENSKY_PASSWORDBackendNo
MARINETRAFFIC_API_KEYBackendNo
STRIPE_SECRET_KEYBackendNo
SECRET_KEY (JWT)BackendNo
DATABASE_URLBackendNo
VITE_API_URLFrontendYes (bundled)
VITE_CESIUM_ION_TOKENFrontendYes (bundled)

Anything with a VITE_* prefix is bundled into the JS payload and is therefore world-readable. Use Cesium Ion tokens scoped to globe rendering, never an admin token.

Scaling considerations

  • Backend is stateless beyond Redis sessions — horizontal scale by adding uvicorn replicas behind a load balancer with sticky WebSocket sessions.
  • PostgreSQL is the common bottleneck before the upstream providers are. Read replicas help for reporting workloads.
  • Redis is small and fast — a single instance handles many users. Replicate for HA.
  • Upstream providers rate-limit per Atlas-account (one shared upstream account for all Atlas users on a hosted instance). Self-hosted instances control their own quota.

Where to read more

  • Self-hosting — deployment shapes.
  • Data providers — what each upstream is for.
  • API keys — how to obtain each credential.
  • Stripe billing — billing webhook flow.
  • Repo: source code is the most authoritative reference. Read the route file for the surface you care about; route files are deliberately short.

Released under the project license. Public sources only — no proprietary or restricted data.