Skip to content

Architecture Overview

Daniel Truong edited this page Sep 6, 2026 · 21 revisions

Architecture Overview

How the EPIC components fit together, and what a request touches on its way through.

System components

flowchart TB
    subgraph Users[" "]
        direction LR
        User["Public user"]
        Staff["EAO staff"]
    end

    subgraph OpenShift["OpenShift 6cdc9e-*"]
        Route["Route"]
        RProxy["eao-nginx (rproxy)"]
        Public["eagle-public<br/>Angular SPA"]
        Admin["eagle-admin<br/>Angular SPA"]
        API["eagle-api<br/>Express"]
        Mongo[("MongoDB")]
        Matview["matview CronJobs<br/>hot 30m, cold 02:00"]
    end

    subgraph Azure["Azure"]
        AFD["Front Door<br/>eagle-public static site"]
        DEMI["demi-api"]
        Cosmos[("Cosmos DB for NoSQL")]
        AISearch[("Azure AI Search<br/>demi-search-*")]
        Notify["eagle-notify"]
    end

    subgraph External["External"]
        Keycloak["BC Gov Keycloak<br/>realm eao-epic"]
        Store[("NRS object store")]
        Engage["ENGAGE met-api<br/>c72cba-*"]
    end

    User --> Route
    Staff --> Route
    Route --> RProxy

    RProxy -->|"/ in test and prod"| AFD
    RProxy -->|"/ in dev"| Public
    RProxy -->|"/admin/"| Admin
    RProxy -->|"/api, /eguide"| API
    RProxy -->|"/analytics"| APIM["demi-apim-&lt;env&gt;"]
    RProxy -->|"/demi-search/search"| DEMI
    RProxy -->|"/demi-projects/{id}"| DEMI

    Public & Admin -.->|"/api/config"| API
    Public & Admin -.->|events| APIM
    Staff -.->|OIDC login| Keycloak
    Admin & API -.->|token validation| Keycloak
    Engage -.->|comment period push| API

    API --> Mongo
    Matview --> Mongo
    API -.->|"PUT /api/eagle/*"| DEMI
    DEMI -.->|"POST /api/events"| Notify
    DEMI --> Cosmos
    DEMI --> AISearch
    DEMI --> Store
    APIM --> Analytics["analytics-api-fc-&lt;env&gt;"]
Loading

Components

eao-nginx

The rproxy service is the edge for every environment hostname. It routes by URL path:

Path Upstream
/ Azure Front Door in test and prod, the in-cluster eagle-public Service in dev
/admin/ eagle-admin
/api, /api/config eagle-api
/eguide eagle-api
/analytics demi-apim-<env> (eagle-analytics)
/eagle-search/ none; localhost:9999 sentinel, 502
/demi-search/search demi-api
/demi-projects/<id> demi-api, as /api/projects/<id>

Upstreams come from NGINX__EPIC__PROXY__* environment variables set per environment in helm/rproxy/values-{dev,test,prod}.yaml. An unset variable renders a http://localhost:9999 sentinel, so dev has no search upstream and falls back to eagle-api's Mongo search.

/demi-search/search is an exact-match location and /demi-projects/<id> a regex that admits a 24-character hex id only. A prefix match would publish demi-api's whole route table anonymously on a gov.bc.ca origin.

Repository: bcgov/eao-nginx. Config template conf.d/server.conf.tmpl. Details in Reverse Proxy.

eagle-public

Angular 21 on Node.js 24. Public interface for viewing environmental assessments: project search, document viewing, comment submission. Fetches runtime configuration from /api/config when the Dockerfile has set configEndpoint = true.

Search for the Project, Document and DocumentChunk datasets goes to the path in SEARCH_API_PATH; every other dataset stays on eagle-api. See Eagle Search.

In test and prod the bundle is served from Azure Storage behind Front Door. See Azure Hosting Cutover.

eagle-admin

Angular 22 on Node.js 24. Staff portal for projects, documents and users. Fetches runtime configuration from /api/config. Comment periods pushed by ENGAGE are read-only here.

eagle-api

Express on Node.js 24, backed by MongoDB. All models share one epic collection and are discriminated by _schemaName.

Endpoints used by every frontend:

Endpoint Purpose
GET /api/config Runtime configuration, unauthenticated, no secrets
GET /api/public/project, /api/public/project/{projId} Public project data
GET /api/public/document, /api/public/document/{docId} Public document metadata
GET /api/public/search Mongo-backed search fallback

Two CronJobs reuse the eagle-api image and connect straight to MongoDB to refresh materialized views: eagle-cron-hot every 30 minutes and eagle-cron-cold at 02:00.

On every project, document and update write, eagle-api pushes the raw record to DEMI with PUT /api/eagle/{projects,documents,updates}/{eagleId} and an X-Api-Key header. The push is fire-and-forget: two attempts, 10-second timeout, failures logged and swallowed. It is disabled unless both DEMI_API_BASE and DEMI_APIM_KEY are set, which is the case in test only.

DEMI

demi-api on Azure App Service with Cosmos DB for NoSQL and Azure AI Search. DEMI is EPIC's central data store for projects, documents, updates and chunks. Nothing writes back into Eagle's MongoDB. See Eagle DEMI.

When an update (RecentActivity) it holds becomes published, demi-api pushes the event to eagle-notify with POST /api/events and an x-functions-key header. See Eagle DEMI for the payload shape and retry behaviour.

eagle-analytics

Azure Functions app behind demi-apim-<env> at /analytics. Receives batched events from eagle-public, eagle-admin and eagle-api, writes them to Log Analytics, and serves the query API behind the dashboard screens in eagle-demi-admin.

See Analytics Architecture.

Keycloak

BC Gov Common Hosted Single Sign-On, realm eao-epic, OpenID Connect. Client eagle-admin-console for eagle-admin user login, eagle-api-console for eagle-api token validation.

Environment URL
Dev https://dev.loginproxy.gov.bc.ca/auth
Test https://test.loginproxy.gov.bc.ca/auth
Prod https://loginproxy.gov.bc.ca/auth

See Keycloak Configuration.

ENGAGE

ENGAGE (epic-engage, c72cba-*) pushes engagement data into eagle-api as comment periods with isMet: true. The traffic is one-way. See ENGAGE–EAGLE Integration.

Request flow

sequenceDiagram
    participant User
    participant RProxy as eao-nginx
    participant Public as eagle-public
    participant API as eagle-api
    participant DB as MongoDB

    User->>RProxy: GET /p/project-123
    RProxy->>Public: GET /p/project-123
    Public-->>User: Angular SPA

    User->>RProxy: GET /api/config
    RProxy->>API: GET /api/config
    API-->>User: Runtime configuration

    User->>RProxy: GET /api/public/project/project-123
    RProxy->>API: GET /api/public/project/project-123
    API->>DB: Query project
    DB-->>API: Project document
    API-->>User: Project JSON
Loading

Runtime configuration

Both Angular apps read their configuration at runtime, so a configuration change needs no rebuild.

  1. The Dockerfile sets configEndpoint = true in env.js.
  2. The browser loads the Angular app.
  3. The app fetches /api/config.
  4. eagle-api answers from the Config document in MongoDB.
  5. The app merges the response over its built-in defaults and renders.

SEARCH_API_PATH rides on this response, which is why moving search between backends is a MongoDB field change and not a deploy. See Configuration Management.

Namespaces

Namespace Purpose
6cdc9e-tools Image registry and build artifacts
6cdc9e-dev Development
6cdc9e-test Test and UAT
6cdc9e-prod Production

Image tags and promotion are covered in Deployment Pipeline.

Security

  • TLS terminates at the OpenShift Route.
  • Staff authenticate against Keycloak with IDIR; eagle-api validates the JWT against the realm JWKS.
  • eagle-api gates every read on a read[] array on the document. 'public' in that array is what makes a record publicly visible.
  • The eagle-api to DEMI push authenticates with a registry API key, sent as X-Api-Key.
  • The demi-api to eagle-notify push authenticates with a function key scoped to one function, sent as x-functions-key.
  • Trivy scans container images for CRITICAL and HIGH vulnerabilities.

Related

Clone this wiki locally