From e169e37b5e3d23a18402b5acad9486a99783b252 Mon Sep 17 00:00:00 2001 From: Sven laptop Date: Thu, 24 Sep 2026 20:20:43 +0200 Subject: [PATCH] Refactored all api endpoints to /bus/xxxxx --- admin_ui/src/api.js | 2 +- admin_ui/src/components/TimetableViewer.jsx | 4 +- auth_api/db.js | 21 +++- auth_api/index.js | 14 ++- employee_api/db.js | 21 +++- employee_api/index.js | 14 ++- error_log_api/db.js | 21 +++- error_log_api/index.js | 11 ++ monitoring_ui/docs/admin_ui/README.md | 14 +-- monitoring_ui/docs/admin_ui/SCHEDULE_API.md | 38 +++--- .../docs/admin_ui/SCHEDULE_MAKER_ENHANCED.md | 8 +- monitoring_ui/docs/azure-project-plan.md | 13 +- .../docs/transit_api/PHASE4_DEFERRED_FIXES.md | 111 ++++++++++++++++++ .../docs/windows_xp_driver_ui/README.md | 4 +- monitoring_ui/package-lock.json | 12 ++ monitoring_ui/package.json | 8 +- monitoring_ui/src/App.jsx | 4 +- 17 files changed, 270 insertions(+), 50 deletions(-) create mode 100644 monitoring_ui/docs/transit_api/PHASE4_DEFERRED_FIXES.md diff --git a/admin_ui/src/api.js b/admin_ui/src/api.js index 4076c92..6e8ac20 100644 --- a/admin_ui/src/api.js +++ b/admin_ui/src/api.js @@ -63,7 +63,7 @@ authClient.interceptors.response.use( export const transitClient = (token) => { const client = axios.create({ - baseURL: TRANSIT_API, + baseURL: `${TRANSIT_API}/bus`, headers: { Authorization: `Bearer ${token}` }, }); diff --git a/admin_ui/src/components/TimetableViewer.jsx b/admin_ui/src/components/TimetableViewer.jsx index a726cdb..7943933 100644 --- a/admin_ui/src/components/TimetableViewer.jsx +++ b/admin_ui/src/components/TimetableViewer.jsx @@ -17,7 +17,7 @@ export default function TimetableViewer({ useEffect(() => { const fetchLines = async () => { try { - const res = await fetch(`${apiBaseUrl}/admin/bus-lines`); + const res = await fetch(`${apiBaseUrl}/bus/admin/bus-lines`); const data = await res.json(); setLines(data.busLines || []); } catch (err) { @@ -33,7 +33,7 @@ export default function TimetableViewer({ setLoading(true); setError(null); try { - let url = `${apiBaseUrl}/schedules/overview?date=${selectedDate}`; + let url = `${apiBaseUrl}/bus/schedules/overview?date=${selectedDate}`; if (selectedLineId) { url += `&lineId=${selectedLineId}`; } diff --git a/auth_api/db.js b/auth_api/db.js index f3e1119..1feca45 100644 --- a/auth_api/db.js +++ b/auth_api/db.js @@ -15,7 +15,26 @@ pool.query('SELECT 1') .catch(err => { console.error('PostgreSQL connection FAILED'); console.error(err); - process.exit(1); + const url = process.env.ERROR_LOG_API_URL || 'https://error.xtransit.testinstance.nl/error_log'; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 5000); + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-internal-api-key': process.env.INTERNAL_API_KEY || '', + }, + body: JSON.stringify({ + service: 'auth_api', + partofservice: 'startup: db connection', + error: err.stack || err.message || String(err), + }), + signal: controller.signal, + }).catch(() => {}) + .finally(() => { + clearTimeout(timer); + process.exit(1); + }); }); module.exports = { diff --git a/auth_api/index.js b/auth_api/index.js index dc64bf9..bcc5198 100644 --- a/auth_api/index.js +++ b/auth_api/index.js @@ -52,12 +52,24 @@ async function reportError({ partofservice, error }) { } catch (_) {} } +function reportUnhandled(context, err) { + const message = err?.message || String(err); + const stack = err?.stack || message; + log("error", context, { error: message, stack }); + void reportError({ partofservice: context, error: stack }); +} + +process.on("uncaughtException", (err) => reportUnhandled("uncaughtException", err)); +process.on("unhandledRejection", (reason) => reportUnhandled("unhandledRejection", reason)); + app.use((req, res, next) => { const originalJson = res.json.bind(res); res.json = (body) => { if (res.statusCode >= 400 && body && typeof body === "object") { const rawError = - body.error || body.message || body.details || `HTTP ${res.statusCode}`; + [body.error, body.message, body.details] + .filter((v) => v !== undefined && v !== null && v !== "") + .join(" - ") || `HTTP ${res.statusCode}`; log("warn", `HTTP ${res.statusCode}`, { path: req.originalUrl, method: req.method, error: rawError }); void reportError({ partofservice: `${req.method} ${req.originalUrl}`, diff --git a/employee_api/db.js b/employee_api/db.js index f3e1119..dc504ec 100644 --- a/employee_api/db.js +++ b/employee_api/db.js @@ -15,7 +15,26 @@ pool.query('SELECT 1') .catch(err => { console.error('PostgreSQL connection FAILED'); console.error(err); - process.exit(1); + const url = process.env.ERROR_LOG_API_URL || 'https://error.xtransit.testinstance.nl/error_log'; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 5000); + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-internal-api-key': process.env.INTERNAL_API_KEY || '', + }, + body: JSON.stringify({ + service: 'employee_api', + partofservice: 'startup: db connection', + error: err.stack || err.message || String(err), + }), + signal: controller.signal, + }).catch(() => {}) + .finally(() => { + clearTimeout(timer); + process.exit(1); + }); }); module.exports = { diff --git a/employee_api/index.js b/employee_api/index.js index 13fd4ab..dd6d11b 100644 --- a/employee_api/index.js +++ b/employee_api/index.js @@ -48,12 +48,24 @@ async function reportError({ partofservice, error }) { } catch (_) {} } +function reportUnhandled(context, err) { + const message = err?.message || String(err); + const stack = err?.stack || message; + log("error", context, { error: message, stack }); + void reportError({ partofservice: context, error: stack }); +} + +process.on("uncaughtException", (err) => reportUnhandled("uncaughtException", err)); +process.on("unhandledRejection", (reason) => reportUnhandled("unhandledRejection", reason)); + app.use((req, res, next) => { const originalJson = res.json.bind(res); res.json = (body) => { if (res.statusCode >= 400 && body && typeof body === "object") { const rawError = - body.error || body.message || body.details || `HTTP ${res.statusCode}`; + [body.error, body.message, body.details] + .filter((v) => v !== undefined && v !== null && v !== "") + .join(" - ") || `HTTP ${res.statusCode}`; log("warn", `HTTP ${res.statusCode}`, { path: req.originalUrl, method: req.method, error: rawError }); void reportError({ partofservice: `${req.method} ${req.originalUrl}`, diff --git a/error_log_api/db.js b/error_log_api/db.js index 95ba93a..d1a6e32 100644 --- a/error_log_api/db.js +++ b/error_log_api/db.js @@ -17,7 +17,26 @@ pool.query('SELECT 1') .catch(err => { console.error('PostgreSQL connection FAILED'); console.error(err); - process.exit(1); + const url = process.env.ERROR_LOG_API_URL || 'https://error.xtransit.testinstance.nl/error_log'; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 5000); + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-internal-api-key': process.env.INTERNAL_API_KEY || '', + }, + body: JSON.stringify({ + service: 'error_log_api', + partofservice: 'startup: db connection', + error: err.stack || err.message || String(err), + }), + signal: controller.signal, + }).catch(() => {}) + .finally(() => { + clearTimeout(timer); + process.exit(1); + }); }); module.exports = { diff --git a/error_log_api/index.js b/error_log_api/index.js index 73e178e..d24efc5 100644 --- a/error_log_api/index.js +++ b/error_log_api/index.js @@ -25,6 +25,7 @@ async function ensureErrorLogTable() { console.log("Error log table ready"); } catch (err) { console.error("Failed to create error log table:", err); + reportError({ partofservice: "startup: ensure error log table", error: err.stack || err.message }); } } @@ -76,6 +77,16 @@ async function reportError({ partofservice, error }) { } catch (_) {} } +function reportUnhandled(context, err) { + const message = err?.message || String(err); + const stack = err?.stack || message; + log("error", context, { error: message, stack }); + void reportError({ partofservice: context, error: stack }); +} + +process.on("uncaughtException", (err) => reportUnhandled("uncaughtException", err)); +process.on("unhandledRejection", (reason) => reportUnhandled("unhandledRejection", reason)); + app.use((req, res, next) => { const originalJson = res.json.bind(res); res.json = (body) => { diff --git a/monitoring_ui/docs/admin_ui/README.md b/monitoring_ui/docs/admin_ui/README.md index e1eba9f..d75a34e 100644 --- a/monitoring_ui/docs/admin_ui/README.md +++ b/monitoring_ui/docs/admin_ui/README.md @@ -3,15 +3,15 @@ The schedule manager uses the weekly template model. Administrators create a departure for a bus line in one of three categories: weekdays, Saturday, or Sunday. The UI creates the corresponding `day_of_week` rows and assigns drivers -through `transit.shifts`, rather than storing a driver on `transit.schedules`. +through `"transit.bus".shifts`, rather than storing a driver on `"transit.bus".schedules`. The active UI uses these transit API endpoints: -- `GET /admin/weekly-schedules` -- `GET /admin/shift-drivers` -- `POST /admin/weekly-schedules` -- `PUT /admin/weekly-schedules/:scheduleId/shift` -- `DELETE /admin/weekly-schedules/:scheduleId` +- `GET /bus/admin/weekly-schedules` +- `GET /bus/admin/shift-drivers` +- `POST /bus/admin/weekly-schedules` +- `PUT /bus/admin/weekly-schedules/:scheduleId/shift` +- `DELETE /bus/admin/weekly-schedules/:scheduleId` # Transit Admin Control Center @@ -142,7 +142,7 @@ When running in Docker, environment variables are injected at runtime via `docke The app communicates with: - **Auth API** (/login, /me) -- **Transit API** (/admin/stops, /admin/bus-lines, /admin/fleet/buses) +- **Transit API** (/bus/admin/stops, /bus/admin/bus-lines, /bus/admin/fleet/buses) - **Error Log API** (/error_log) All requests include JWT Bearer token for authentication. diff --git a/monitoring_ui/docs/admin_ui/SCHEDULE_API.md b/monitoring_ui/docs/admin_ui/SCHEDULE_API.md index c4d8de8..5905e3d 100644 --- a/monitoring_ui/docs/admin_ui/SCHEDULE_API.md +++ b/monitoring_ui/docs/admin_ui/SCHEDULE_API.md @@ -8,20 +8,20 @@ The Transit API now includes a comprehensive schedule management system with bot The active admin UI uses weekly schedule templates. `dayOfWeek` follows the JavaScript/PostgreSQL convention: `0` Sunday, `1` through `5` weekdays, and `6` -Saturday. Driver assignment is stored in `transit.shifts` and references +Saturday. Driver assignment is stored in `"transit.bus".shifts` and references `workers.users(id)`. #### Get weekly schedules ``` -GET /admin/weekly-schedules +GET /bus/admin/weekly-schedules Response: { schedules: [...] } ``` #### Get shift drivers ``` -GET /admin/shift-drivers +GET /bus/admin/shift-drivers Response: { drivers: [{ id, name }] } ``` @@ -32,7 +32,7 @@ Response: { drivers: [{ id, name }] } #### Create multiple departures ```text -POST /admin/weekly-schedules/bulk +POST /bus/admin/weekly-schedules/bulk Body: { busLineId: 1, dayOfWeeks: [1, 2, 3, 4, 5], @@ -43,7 +43,7 @@ Body: { The API creates one schedule row for every day/time combination and derives the end time from the bus line's configured duration. -POST /admin/weekly-schedules +POST /bus/admin/weekly-schedules Body: { busLineId: 1, dayOfWeek: 1, @@ -58,7 +58,7 @@ driverId: 12 ``` -PUT /admin/weekly-schedules/:scheduleId/shift +PUT /bus/admin/weekly-schedules/:scheduleId/shift Body: { driverId: 12 } ``` @@ -73,7 +73,7 @@ historical per-week assignment. Add `week_start` to support that later. ``` -GET /admin/schedules +GET /bus/admin/schedules Response: { schedules: [...] } ``` @@ -82,7 +82,7 @@ Response: { schedules: [...] } ``` -POST /admin/schedules +POST /bus/admin/schedules Body: { busLineId: number, busId: number, @@ -98,7 +98,7 @@ Response: { schedule: {...} } ``` -DELETE /admin/schedules/:scheduleId +DELETE /bus/admin/schedules/:scheduleId Response: { success: true, schedule: {...} } ``` @@ -109,7 +109,7 @@ Response: { success: true, schedule: {...} } ``` -GET /schedules?date=2026-05-06&lineId=1 +GET /bus/schedules?date=2026-05-06&lineId=1 Query params: - date: YYYY-MM-DD (optional) @@ -122,7 +122,7 @@ Query params: ``` -GET /schedules/:scheduleId +GET /bus/schedules/:scheduleId Response: { schedule: {...} } ``` @@ -131,7 +131,7 @@ Response: { schedule: {...} } ``` -GET /schedules/:scheduleId/stops +GET /bus/schedules/:scheduleId/stops Response: { stops: [...] } Shows all stops on the bus line with order, location, and ETA. @@ -196,9 +196,9 @@ ADMIN_USER_CODES=42 DEV_MOCK=1 JWT_SECRET=devsecret npm --prefix ./transit_api s ## Database Schema (Production) ```sql -CREATE TABLE transit.schedules ( +CREATE TABLE "transit.bus".schedules ( id BIGINT PRIMARY KEY, - bus_line_id BIGINT REFERENCES transit.bus_lines(id), + bus_line_id BIGINT REFERENCES "transit.bus".bus_lines(id), start_time TIME, end_time TIME, status VARCHAR(20) DEFAULT 'planned', @@ -207,9 +207,9 @@ CREATE TABLE transit.schedules ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE TABLE transit.shifts ( +CREATE TABLE "transit.bus".shifts ( id BIGINT PRIMARY KEY, - schedule_id BIGINT REFERENCES transit.schedules(id) ON DELETE CASCADE, + schedule_id BIGINT REFERENCES "transit.bus".schedules(id) ON DELETE CASCADE, driver_id BIGINT REFERENCES workers.users(id) ); ``` @@ -246,7 +246,7 @@ The schedule manager uses: ### Create a schedule (Admin) ```bash -curl -X POST https://transit.xtransit.testinstance.nl/admin/schedules \ +curl -X POST https://transit.xtransit.testinstance.nl/bus/admin/schedules \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ @@ -261,13 +261,13 @@ curl -X POST https://transit.xtransit.testinstance.nl/admin/schedules \ ### Get today's schedules (Public) ```bash -curl https://transit.xtransit.testinstance.nl/schedules?date=$(date +%Y-%m-%d) +curl https://transit.xtransit.testinstance.nl/bus/schedules?date=$(date +%Y-%m-%d) ``` ### Get schedules for specific line (Public) ```bash -curl https://transit.xtransit.testinstance.nl/schedules?lineId=3 +curl https://transit.xtransit.testinstance.nl/bus/schedules?lineId=3 ``` ## Future Enhancements diff --git a/monitoring_ui/docs/admin_ui/SCHEDULE_MAKER_ENHANCED.md b/monitoring_ui/docs/admin_ui/SCHEDULE_MAKER_ENHANCED.md index eccafce..0362829 100644 --- a/monitoring_ui/docs/admin_ui/SCHEDULE_MAKER_ENHANCED.md +++ b/monitoring_ui/docs/admin_ui/SCHEDULE_MAKER_ENHANCED.md @@ -383,15 +383,15 @@ if (DEV_MOCK) { ## Production Database Support -Voor productie (niet DEV_MOCK) moeten deze kolommen bestaan in `transit.schedules`: +Voor productie (niet DEV_MOCK) moeten deze kolommen bestaan in `"transit.bus".schedules`: ```sql -ALTER TABLE transit.schedules ADD COLUMN IF NOT EXISTS weekdays JSONB; +ALTER TABLE "transit.bus".schedules ADD COLUMN IF NOT EXISTS weekdays JSONB; ``` Queries gebruiken: ```sql -SELECT ... FROM transit.schedules WHERE weekdays::text LIKE '1' -- for Maandag +SELECT ... FROM "transit.bus".schedules WHERE weekdays::text LIKE '1' -- for Maandag ``` --- @@ -443,7 +443,7 @@ SELECT ... FROM transit.schedules WHERE weekdays::text LIKE '1' -- for Maandag 2. **Tijdzones**: Alles in UTC - Toekomst: Voeg timezone support toe voor meerdere regio's -3. **Database Migrations**: `transit.schedules` moet `weekdays` kolom hebben +3. **Database Migrations**: `"transit.bus".schedules` moet `weekdays` kolom hebben - Status: Handmatig SQL nodig voor productie 4. **Bestuurders Toewijzing**: Geen conflict detectie diff --git a/monitoring_ui/docs/azure-project-plan.md b/monitoring_ui/docs/azure-project-plan.md index faa108a..64b796b 100644 --- a/monitoring_ui/docs/azure-project-plan.md +++ b/monitoring_ui/docs/azure-project-plan.md @@ -114,18 +114,23 @@ Command Center — service health: Service Catalog — service capability: | Service | Capability | Route | Access | |---------|------------|-------|--------| -| Transit API | Daily schedules | `/schedule/daily` | Authenticated | -| Transit API | Route detail | `/routes/:routeId` | Authenticated | +| Transit API | Daily schedules | `/bus/schedule/daily` | Authenticated | +| Transit API | Route detail | `/bus/routes/:routeId` | Authenticated | | Employee API | Create employee | `/createemployee` | Admin | | Error Log API | Read error log | `/error_log` | Admin | Request Lab — request preset: | Service | Method | Path | Expected result | |---------|--------|------|-----------------| -| Transit API | GET | `/schedules/overview` | Schedule summary | -| Transit API | GET | `/admin/drivers` | Driver roster | +| Transit API | GET | `/bus/schedules/overview` | Schedule summary | +| Transit API | GET | `/bus/admin/drivers` | Driver roster | | Employee API | GET | `/health` | Service health | +> **Note:** Transit API bus endpoints are now served under the `/bus/` prefix. +> Enter `/bus/...` paths in the Request Lab for bus routes; `/health` stays +> unprefixed. The legacy unprefixed bus paths still work but return a +> `Deprecation` response header. + Incident Log — normalized incident: | Time | Service | Code | Message | Severity | |------|---------|------|---------|----------| diff --git a/monitoring_ui/docs/transit_api/PHASE4_DEFERRED_FIXES.md b/monitoring_ui/docs/transit_api/PHASE4_DEFERRED_FIXES.md new file mode 100644 index 0000000..db9f162 --- /dev/null +++ b/monitoring_ui/docs/transit_api/PHASE4_DEFERRED_FIXES.md @@ -0,0 +1,111 @@ +# Phase 4 — Deferred Fixes & DB/Code Alignment + +**Goal:** clear the items Phase 3 deliberately preserved or surfaced, and which +are real behavioral defects once the code runs against the live PostgreSQL at +`PG_HOST`. **No endpoint or JSON shape changes** unless a stage explicitly +declares one and updates the docs/READMEs accordingly. + +**Prerequisite:** Phase 3 module split landed (repo layer, bootstrap `index.js`, +`/bus/` + legacy alias mounts, `search_path="transit.bus"`). + +## 1. Verified-failing items (triage from Phase 3 verification) + +| # | Item | Where | Current behavior | Why it matters | +| --- | --- | --- | --- | --- | +| 1 | `GET /bus/schedules` → 500 `column s.bus_id does not exist` | `repo/schedules.js` list query | Query expects a `bus_id` column the live DB lacks | The core read endpoint is broken on the live DB in both old and new code | +| 2 | Mock `markStopPassed` → 500 `delayMinutes is not defined` | `repo/schedules.js` / `.mock.js` | Mock branch reads an undeclared variable | `/driver/schedules/:id/stops/:order/passed` always 500s in mock mode | +| 3 | `bulk` endpoints + lines reorder/open `db.pool.connect()` before `try` | `repo/weekly.js`, `repo/adminSchedules.js`, `repo/lines.js` | With PG unreachable they hang instead of failing fast | Production blast-radius if the DB flaps | +| 4 | Dead `overview` enrichment branch | original handler; NOT copied to `repo/schedules.mock.js` | Unreachable code in original | Left as-is during Phase 3; decide delete or rework | +| 5 | Legacy root-alias mount | `index.js` (Double-mount `/` + `Deprecation` header) | Serves every route twice | Only Windows XP driver still needs it; plan its removal | + +## 2. Rules + +1. One fix per stage; verify against **live DB** where the stage touches PG. +2. Every mock-side fix MUST be mirrored with identical behavior in the PG branch + (and vice versa) so both modes stay byte-identical for the same logical input. +3. Do not normalize mock vs PG response casing (that was the Phase 3 contract); + fix *fairness* (both modes behave alike), not shape. +4. Any endpoint whose JSON shape must change requires a doc update in this file + and in `transit_api/README.md`, and gets a note for the consuming clients + (`admin_ui`, `monitoring_ui`, `windows_xp_driver_ui`). +5. `node --check` + `grep -rn DEV_MOCK index.js routes/` (must stay clean) after + every stage. + +## 3. Stages + +### Stage A — alive `schedules` read path (DB/query alignment) + +- Inspect real schema: `\d "transit.bus".schedules` (and `shifts`) at + `PG_HOST`. Determine whether `bus_id` was renamed/never added and whether the + code or the data is canonical (other clients: `admin_ui`, `monitoring_ui` + also read schedules — check what columns they rely on). +- Decide: (a) add/migrate the column (`ALTER TABLE` + backfill from + `bus_line_id`/related), or (b) adjust the query. Prefer fixing the data model + so the API and all consumers see one shape. +- Re-run the Phase 3 live parity sweep; `GET /bus/schedules*` must now return + 200 with real rows in BOTH original-equivalent and refactored code paths. +- Gate: `GET /bus/schedules`, `/bus/schedules/overview`, `/bus/schedules/:id` + all 200 on live DB; `GET /bus/schedules/recurring` unchanged in response + shape if it already worked. + +### Stage B — mock `markStopPassed` fairness + +- Fix the undeclared `delayMinutes` read in the mock branch (compute the same + duration the PG branch derives from the `schedule_stop_progress` row/stop + times). Produces 200 instead of 500 in mock mode, matching PG mode. +- Gate: harness2 (mock mode) for `POST .../passed` → 200; PG mode unchanged. + +### Stage C — fail-fast PG connects + +- Move `db.pool.connect()` inside `try` for the open-connect endpoints so an + unreachable DB yields the same 500/timeout the non-open endpoints already do; + preserve short-circuit ordering so `try` + connect can't double-acquire. +- Verify order/behavior parity pre/post on live DB with PG up; with PG down the + endpoint fails fast instead of hanging. +- Gate: same responses as Phase 3 harness when PG reachable; NEW: explicit + PG-down test shows quick 500 (differential documented here). + +### Stage D — dead-code cleanup + +- Delete the unreachable `overview` enrichment in the original-style handler + (now only exists as the pg-branch / historical code); confirm no hidden + column the live DB lacks was being referenced only there. +- Recommend: keep the `schedule_stop_progress` upsert the pg branch needs; + remove nothing else. +- Gate: `grep` clean; overview 200 on live DB. + +### Stage E — retire legacy alias (deferred decision) + +- Reconfirm at project level whether the Windows XP driver is still deployed + (`windows_xp_driver_ui/driver_xp.c`). If yes: **do not do this stage**, keep + the `Deprecation` header and re-schedule. If no: remove the `/` double-mount + from `index.js`, add 404 for legacy paths, update `BUS_API_PREFIX_REFACTOR.md`. +- Gate: `GET /schedules` → 404; `GET /bus/schedules` → 200; no route uses `/`. + +## 4. Verification (gate after every stage) + +```bash +cd transit_api +node --check index.js && node --check lib/*.js routes/*.js repo/*.js mock/store.js +grep -rn DEV_MOCK index.js routes/ || echo "clean" +# live DB (PG at .env PG_HOST): +node index.js # prod +curl -s http://localhost:5001/health # database:"ok" +curl -s http://localhost:5001/bus/schedules # stage A: 200 JSON +# mock: +DEV_MOCK=1 node index.js # stage B/C mock fairness +``` + +Re-run `/tmp/opencode/live-parity.js` (read-only old-vs-new) and the Phase 3 +harness after each stage; all prior byte-parity results must stay green except +for the specific fix the stage documents. + +## 5. Definition of done + +1. `GET /bus/schedules`, `.overview`, `/:id`, recurring all 200 on live DB. +2. Mock and PG modes behave identically for every endpoint (harness, not just + shape). +3. PG-down produces fast, clear 500s (no hangs) on the connect-before-try + endpoints. +4. No dead code; legacy alias decision recorded in this repo. +5. Per-stage differential reports appended here. \ No newline at end of file diff --git a/monitoring_ui/docs/windows_xp_driver_ui/README.md b/monitoring_ui/docs/windows_xp_driver_ui/README.md index 10510bb..e368374 100644 --- a/monitoring_ui/docs/windows_xp_driver_ui/README.md +++ b/monitoring_ui/docs/windows_xp_driver_ui/README.md @@ -15,8 +15,8 @@ For Windows XP compatibility, the client temporarily uses the LAN services: This HTTP fallback avoids the TLS 1.2/certificate limitations of Windows XP. -Schedules are assigned through `transit.shifts`; the client does not expect a -`driver_id`, `bus_id`, or `weekdays` column on `transit.schedules`. +Schedules are assigned through `"transit.bus".shifts`; the client does not expect a +`driver_id`, `bus_id`, or `weekdays` column on `"transit.bus".schedules`. ## Build diff --git a/monitoring_ui/package-lock.json b/monitoring_ui/package-lock.json index ee0a033..283ccc2 100644 --- a/monitoring_ui/package-lock.json +++ b/monitoring_ui/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@vitejs/plugin-react": "5.0.0", + "marked": "^18.0.14", "react": "18.2.0", "react-dom": "18.2.0", "vite": "5.0.0" @@ -1225,6 +1226,17 @@ "yallist": "^3.0.2" } }, + "node_modules/marked": { + "version": "18.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.14.tgz", + "integrity": "sha512-mBHK6FBHuBAlhgRe88w9F0O1AbwwXJUcQibUbC/QcdTbVGAD7aWza+xt3N6oT/jCZx3/OMeS+8rnuiHZcQ9s7A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/monitoring_ui/package.json b/monitoring_ui/package.json index f50b0c9..611358f 100644 --- a/monitoring_ui/package.json +++ b/monitoring_ui/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@vitejs/plugin-react": "5.0.0", - "vite": "5.0.0", + "marked": "^18.0.14", "react": "18.2.0", - "react-dom": "18.2.0" - }, - "devDependencies": {} + "react-dom": "18.2.0", + "vite": "5.0.0" + } } diff --git a/monitoring_ui/src/App.jsx b/monitoring_ui/src/App.jsx index 94c9b0b..2fcd7b5 100644 --- a/monitoring_ui/src/App.jsx +++ b/monitoring_ui/src/App.jsx @@ -79,13 +79,13 @@ const testerRoutes = [ { service: "transit", method: "GET", - path: "/public/schedules/1/progress", + path: "/bus/public/schedules/1/progress", label: "Public schedule progress", }, { service: "transit", method: "GET", - path: "/schedule/daily?date=2026-09-20", + path: "/bus/schedule/daily?date=2026-09-20", label: "Driver daily schedule", }, {