Staging environment + FleetOps split #17

Open
kianiadee wants to merge 23 commits from feat/staging-fleetops-architecture into main
Showing only changes of commit 2603f0e726 - Show all commits

View file

@ -82,7 +82,7 @@ Traefik host `fleetapi.rahamafresh.com`). Staging mirrors it:
| Port | 8890 | **8891** |
| Code mount | `~/dashboard_api/` | `~/dashboard_api_staging/` (WIP checkout) |
| Deploy script | `~/deploy_dashboard_api.sh` | **`deploy_dashboard_api_staging.sh`** (checked into this repo) |
| DB role | app role (read/write) | **read-only** (`dashboard_ro` / `grafana_ro`) |
| DB role | app role (read/write) | **read-only** `dashboard_ro` (dedicated) |
| `v_trips` refresher | **owns it** | **disabled** |
| CORS origins | `fleetnow.rahamafresh.com`, `fleetintelligence.…`, `liveposition.…`, **+ `fleetops.rahamafresh.com`** | `fleetnow.fivetitude.com`, `fleetops.fivetitude.com` |
@ -105,9 +105,9 @@ the JSONB/GeoJSON return style of the existing `/webhook/*` routes:
| `GET /analytics/filters` | `reporting.v_filter_*` (alias of `GET /webhook/fleet-dashboard`) |
Aggregations that aren't thin wrappers get a **new numbered migration** — never edit an applied
one. The fuel roll-up ships as `migrations/17_fleetops_fuel_view.sql` (the live migration head is
**16**, not 13 as older docs imply); it also `GRANT`s `SELECT` to `grafana_ro` for the staging
read-only role.
one. The fuel roll-up ships as `migrations/17_fleetops_fuel_view.sql` (the live migration head was
**16**, not 13 as older docs imply; 17 + 18 are now applied). `dashboard_ro` reads `v_fuel_daily`
via the schema-wide `SELECT` grant in `scripts/dashboard_ro_role.sql`.
> **Reuse the existing reporting layer.** The analytics building blocks are `reporting.*`
> (migrations 11/14) and the surviving `tracksolid.v_*` views (migration 07). The `ops.*` and
@ -163,9 +163,16 @@ Production is touched **only** by a merge to `main`. That branch discipline is w
Staging hits the **production database**, so isolation is enforced at the **DB-role level**,
not by a separate DB:
- The staging `dashboard_api` connects as a **read-only role** — reuse `grafana_ro`, or add a
dedicated `dashboard_ro` with `GRANT SELECT` on `reporting.*` and the `tracksolid.v_*`
views. Accidental writes from staging are then impossible.
- The staging `dashboard_api` connects as a **dedicated read-only role `dashboard_ro`**
(`scripts/dashboard_ro_role.sql` + `scripts/bootstrap_dashboard_ro.sh`). It grants exactly
what the API reads — `SELECT` on `reporting.*` + `tracksolid.*`, an explicit `SELECT` on the
`reporting.v_trips` **materialized view** (matviews aren't covered by `GRANT ... ON ALL
TABLES`), `EXECUTE` on the `reporting.fn_*` map functions, and `ALTER DEFAULT PRIVILEGES` so
future objects are auto-readable ("dynamic"). No write/REFRESH privilege, so accidental writes
are impossible. The password is generated on the host into `~/.dashboard_ro.pw` (0600), never
in the repo. **Two-stage:** stage 1 backs the *staging* bridge (done); stage 2 migrates the
*live prod* `fleetapi.rahamafresh.com` connection off the app role onto `dashboard_ro` (which
already grants the full read surface, incl. the live `fn_*` path).
- The **`reporting.v_trips` materialized-view refresher is disabled on staging** — production
owns it. The refresher needs write perms and is already pg-advisory-lock guarded (key
`920_145`, FIX-D02); a read-only staging role would only log errors, so disable it explicitly
@ -182,7 +189,7 @@ client's production domains **last**.
| Phase | Scope | Exit criterion |
|---|---|---|
| **0 — Foundation** | This document; migrate all Coolify apps to Forgejo webhook deploys; provision the read-only DB role | Every existing Coolify app redeploys via webhook; read-only role can `SELECT` `reporting.*` + `tracksolid.v_*` and nothing else |
| **0 — Foundation** | This document; provision the read-only `dashboard_ro` role (**done**); migrate all Coolify apps to Forgejo webhook deploys (**pending**) | `dashboard_ro` can `SELECT` `reporting.*` + `tracksolid.*` + `v_trips` and nothing else; every Coolify app redeploys via webhook |
| **1 — Staging backbone** | Staging `dashboard_api` bridge (`deploy_dashboard_api_staging.sh`, 8891, `fleetapi.fivetitude.com`, read-only, refresher off, staging CORS) | `curl https://fleetapi.fivetitude.com/health` ok; verifiably read-only; no staging rows in `reporting.refresh_log` |
| **2 — FleetNow staging** | FleetNow repo: `staging` branch + parameterized API base + `fleetnow.fivetitude.com` Coolify app | Renders against staging API; `staging` push deploys staging only, `main` merge deploys prod only; prod FleetNow untouched |
| **3 — FleetOps backend** | `/analytics/*` endpoints in `dashboard_api_rev.py` + `migrations/17_fleetops_fuel_view.sql`; refresher made disable-able (`VTRIPS_REFRESH_INTERVAL_S<=0`); tested on the staging API | Every route returns correct shape on `fleetapi.fivetitude.com`; fuel route returns "needs data" flags |