Initial implementation of the public trips dashboard: - db/migrations/001..005: read-only viz_anon role + thin trips_viz_v1 view + three SECURITY DEFINER RPCs (trips_for_day, trips_for_range, list_cost_centres). Builds path on demand from position_history; coalesces missing cost_centre to 'Unassigned'. Smoke-tested against staging: 982 trips / 13 cost centres for 2026-04-29. - compose/: PostgREST v12 service + trips_web Caddy service. CORS allow-listed to the web FQDN; viz_anon role is the only authorization. - web/: Vite + React + TS SPA. deck.gl TripsLayer animated over PathLayer (whole route in low opacity), Mapbox GL dark base map, Zustand store, TanStack Query for fetching. Sidebar = date controls + cost-centre multi-select + vehicle drilldown. Timebar = scrubber with 1x/10x/60x/600x speeds. tsc + vite build clean. - README + design doc updated to match the verified schema (path lives in tracksolid.position_history, vehicle key is imei, no down-sampling needed at observed volume).
83 lines
3 KiB
Markdown
83 lines
3 KiB
Markdown
# deckgl_tracksolid
|
|
|
|
Public dashboard that animates a day's vehicle trips from `tracksolid_db`,
|
|
colored and filtered by `cost_centre`. Stack: deck.gl `TripsLayer` over
|
|
Mapbox GL, fed by PostgREST in front of TimescaleDB.
|
|
|
|
```
|
|
React + deck.gl SPA → PostgREST → tracksolid_db (Timescale)
|
|
(web/) (compose/) public.trips_viz_v1
|
|
public.trips_for_day(date, text[])
|
|
public.trips_for_range(date, date, text[])
|
|
public.list_cost_centres()
|
|
```
|
|
|
|
The full design lives in `trips_deckgl_tracksolid.md` — read that first.
|
|
|
|
## Layout
|
|
|
|
| Path | Purpose |
|
|
| --- | --- |
|
|
| `db/migrations/` | Five SQL files. Idempotent. Apply in order. Add only `public.*` objects. |
|
|
| `compose/` | `docker-compose.yaml` for the PostgREST API + the built web container. |
|
|
| `web/` | Vite + React + TS SPA. Renders `TripsLayer` + `PathLayer` over Mapbox. |
|
|
| `trips_deckgl_tracksolid.md` | Design doc / plan. |
|
|
|
|
## First-time setup
|
|
|
|
1. **Apply migrations** to `tracksolid_db`:
|
|
```bash
|
|
psql "$ADMIN_DSN" \
|
|
-f db/migrations/001_viz_anon_role.sql \
|
|
-f db/migrations/002_trips_viz_view.sql \
|
|
-f db/migrations/003_trips_for_day_rpc.sql \
|
|
-f db/migrations/004_trips_for_range_rpc.sql \
|
|
-f db/migrations/005_list_cost_centres_rpc.sql
|
|
```
|
|
2. **Set the `viz_anon` login password** out of band (do not commit):
|
|
```sql
|
|
ALTER ROLE viz_anon LOGIN PASSWORD '<generate one>';
|
|
```
|
|
3. **Configure env**:
|
|
```bash
|
|
cp compose/.env.example compose/.env
|
|
# fill in VIZ_DATA_PASSWORD, VITE_MAPBOX_TOKEN, …
|
|
cp web/.env.example web/.env.local
|
|
# fill in VITE_API_URL (e.g. http://localhost:3000) and VITE_MAPBOX_TOKEN
|
|
```
|
|
|
|
## Local development
|
|
|
|
```bash
|
|
# 1. Start PostgREST against the staging DB
|
|
cd compose && docker compose up postgrest
|
|
|
|
# 2. Run the web app against it
|
|
cd web && pnpm install && pnpm dev
|
|
# → http://localhost:5173
|
|
```
|
|
|
|
The web app reads `VITE_API_URL` (default `http://localhost:3000`) and
|
|
`VITE_MAPBOX_TOKEN`. Without a Mapbox token the deck.gl layers still render
|
|
on a black background — fine for testing data, ugly for demos.
|
|
|
|
## Production deployment (Coolify)
|
|
|
|
Two services live alongside the existing Dekart stack:
|
|
|
|
- `postgrest` — read-only API at `api.trips.rahamafresh.com`. CORS allow-listed
|
|
to the web FQDN.
|
|
- `trips_web` — Caddy serving the built SPA at `trips.rahamafresh.com`.
|
|
|
|
Both inherit env vars from the Coolify resource panel — see `compose/.env.example`
|
|
for the canonical list.
|
|
|
|
## Security boundary
|
|
|
|
The `viz_anon` role has **no grants on `tracksolid.*`** — verified in setup. It
|
|
can only call the four RPCs in `public`, which run `SECURITY DEFINER` and only
|
|
expose the columns listed in `trips_viz_v1`. Direct queries against any
|
|
`tracksolid` table return `permission denied for schema tracksolid`.
|
|
|
|
If the public dashboard ever needs to be locked down, add Traefik basic-auth in
|
|
Coolify (same pattern Dekart uses) — no app-level changes needed.
|