Refactored all api endpoints to /bus/xxxxx
Build and Push Docker Images / detect-changes (push) Successful in 20s
Build and Push Docker Images / build-auth-api (push) Successful in 2m2s
Build and Push Docker Images / build-employee-api (push) Successful in 2m2s
Build and Push Docker Images / build-transit-api (push) Successful in 2m8s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m58s
Build and Push Docker Images / build-monitoring-ui (push) Has been cancelled
Build and Push Docker Images / build-admin-ui (push) Has been cancelled
Build and Push Docker Images / deploy-test-node (push) Has been cancelled

This commit is contained in:
Sven laptop
2026-09-24 20:20:43 +02:00
parent 7d55580d87
commit e169e37b5e
17 changed files with 270 additions and 50 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ authClient.interceptors.response.use(
export const transitClient = (token) => { export const transitClient = (token) => {
const client = axios.create({ const client = axios.create({
baseURL: TRANSIT_API, baseURL: `${TRANSIT_API}/bus`,
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
+2 -2
View File
@@ -17,7 +17,7 @@ export default function TimetableViewer({
useEffect(() => { useEffect(() => {
const fetchLines = async () => { const fetchLines = async () => {
try { try {
const res = await fetch(`${apiBaseUrl}/admin/bus-lines`); const res = await fetch(`${apiBaseUrl}/bus/admin/bus-lines`);
const data = await res.json(); const data = await res.json();
setLines(data.busLines || []); setLines(data.busLines || []);
} catch (err) { } catch (err) {
@@ -33,7 +33,7 @@ export default function TimetableViewer({
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
let url = `${apiBaseUrl}/schedules/overview?date=${selectedDate}`; let url = `${apiBaseUrl}/bus/schedules/overview?date=${selectedDate}`;
if (selectedLineId) { if (selectedLineId) {
url += `&lineId=${selectedLineId}`; url += `&lineId=${selectedLineId}`;
} }
+19
View File
@@ -15,8 +15,27 @@ pool.query('SELECT 1')
.catch(err => { .catch(err => {
console.error('PostgreSQL connection FAILED'); console.error('PostgreSQL connection FAILED');
console.error(err); console.error(err);
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); process.exit(1);
}); });
});
module.exports = { module.exports = {
query: (text, params) => pool.query(text, params), query: (text, params) => pool.query(text, params),
+13 -1
View File
@@ -52,12 +52,24 @@ async function reportError({ partofservice, error }) {
} catch (_) {} } 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) => { app.use((req, res, next) => {
const originalJson = res.json.bind(res); const originalJson = res.json.bind(res);
res.json = (body) => { res.json = (body) => {
if (res.statusCode >= 400 && body && typeof body === "object") { if (res.statusCode >= 400 && body && typeof body === "object") {
const rawError = 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 }); log("warn", `HTTP ${res.statusCode}`, { path: req.originalUrl, method: req.method, error: rawError });
void reportError({ void reportError({
partofservice: `${req.method} ${req.originalUrl}`, partofservice: `${req.method} ${req.originalUrl}`,
+19
View File
@@ -15,8 +15,27 @@ pool.query('SELECT 1')
.catch(err => { .catch(err => {
console.error('PostgreSQL connection FAILED'); console.error('PostgreSQL connection FAILED');
console.error(err); console.error(err);
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); process.exit(1);
}); });
});
module.exports = { module.exports = {
query: (text, params) => pool.query(text, params), query: (text, params) => pool.query(text, params),
+13 -1
View File
@@ -48,12 +48,24 @@ async function reportError({ partofservice, error }) {
} catch (_) {} } 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) => { app.use((req, res, next) => {
const originalJson = res.json.bind(res); const originalJson = res.json.bind(res);
res.json = (body) => { res.json = (body) => {
if (res.statusCode >= 400 && body && typeof body === "object") { if (res.statusCode >= 400 && body && typeof body === "object") {
const rawError = 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 }); log("warn", `HTTP ${res.statusCode}`, { path: req.originalUrl, method: req.method, error: rawError });
void reportError({ void reportError({
partofservice: `${req.method} ${req.originalUrl}`, partofservice: `${req.method} ${req.originalUrl}`,
+19
View File
@@ -17,8 +17,27 @@ pool.query('SELECT 1')
.catch(err => { .catch(err => {
console.error('PostgreSQL connection FAILED'); console.error('PostgreSQL connection FAILED');
console.error(err); console.error(err);
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); process.exit(1);
}); });
});
module.exports = { module.exports = {
query: (text, params) => pool.query(text, params), query: (text, params) => pool.query(text, params),
+11
View File
@@ -25,6 +25,7 @@ async function ensureErrorLogTable() {
console.log("Error log table ready"); console.log("Error log table ready");
} catch (err) { } catch (err) {
console.error("Failed to create error log table:", 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 (_) {} } 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) => { app.use((req, res, next) => {
const originalJson = res.json.bind(res); const originalJson = res.json.bind(res);
res.json = (body) => { res.json = (body) => {
+7 -7
View File
@@ -3,15 +3,15 @@
The schedule manager uses the weekly template model. Administrators create a The schedule manager uses the weekly template model. Administrators create a
departure for a bus line in one of three categories: weekdays, Saturday, or 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 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: The active UI uses these transit API endpoints:
- `GET /admin/weekly-schedules` - `GET /bus/admin/weekly-schedules`
- `GET /admin/shift-drivers` - `GET /bus/admin/shift-drivers`
- `POST /admin/weekly-schedules` - `POST /bus/admin/weekly-schedules`
- `PUT /admin/weekly-schedules/:scheduleId/shift` - `PUT /bus/admin/weekly-schedules/:scheduleId/shift`
- `DELETE /admin/weekly-schedules/:scheduleId` - `DELETE /bus/admin/weekly-schedules/:scheduleId`
# Transit Admin Control Center # Transit Admin Control Center
@@ -142,7 +142,7 @@ When running in Docker, environment variables are injected at runtime via `docke
The app communicates with: The app communicates with:
- **Auth API** (/login, /me) - **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) - **Error Log API** (/error_log)
All requests include JWT Bearer token for authentication. All requests include JWT Bearer token for authentication.
+19 -19
View File
@@ -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 The active admin UI uses weekly schedule templates. `dayOfWeek` follows the
JavaScript/PostgreSQL convention: `0` Sunday, `1` through `5` weekdays, and `6` 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)`. `workers.users(id)`.
#### Get weekly schedules #### Get weekly schedules
``` ```
GET /admin/weekly-schedules GET /bus/admin/weekly-schedules
Response: { schedules: [...] } Response: { schedules: [...] }
``` ```
#### Get shift drivers #### Get shift drivers
``` ```
GET /admin/shift-drivers GET /bus/admin/shift-drivers
Response: { drivers: [{ id, name }] } Response: { drivers: [{ id, name }] }
``` ```
@@ -32,7 +32,7 @@ Response: { drivers: [{ id, name }] }
#### Create multiple departures #### Create multiple departures
```text ```text
POST /admin/weekly-schedules/bulk POST /bus/admin/weekly-schedules/bulk
Body: { Body: {
busLineId: 1, busLineId: 1,
dayOfWeeks: [1, 2, 3, 4, 5], 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 The API creates one schedule row for every day/time combination and derives the
end time from the bus line's configured duration. end time from the bus line's configured duration.
POST /admin/weekly-schedules POST /bus/admin/weekly-schedules
Body: { Body: {
busLineId: 1, busLineId: 1,
dayOfWeek: 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 } 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: [...] } Response: { schedules: [...] }
``` ```
@@ -82,7 +82,7 @@ Response: { schedules: [...] }
``` ```
POST /admin/schedules POST /bus/admin/schedules
Body: { Body: {
busLineId: number, busLineId: number,
busId: number, busId: number,
@@ -98,7 +98,7 @@ Response: { schedule: {...} }
``` ```
DELETE /admin/schedules/:scheduleId DELETE /bus/admin/schedules/:scheduleId
Response: { success: true, schedule: {...} } 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: Query params:
- date: YYYY-MM-DD (optional) - date: YYYY-MM-DD (optional)
@@ -122,7 +122,7 @@ Query params:
``` ```
GET /schedules/:scheduleId GET /bus/schedules/:scheduleId
Response: { schedule: {...} } Response: { schedule: {...} }
``` ```
@@ -131,7 +131,7 @@ Response: { schedule: {...} }
``` ```
GET /schedules/:scheduleId/stops GET /bus/schedules/:scheduleId/stops
Response: { stops: [...] } Response: { stops: [...] }
Shows all stops on the bus line with order, location, and ETA. 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) ## Database Schema (Production)
```sql ```sql
CREATE TABLE transit.schedules ( CREATE TABLE "transit.bus".schedules (
id BIGINT PRIMARY KEY, 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, start_time TIME,
end_time TIME, end_time TIME,
status VARCHAR(20) DEFAULT 'planned', status VARCHAR(20) DEFAULT 'planned',
@@ -207,9 +207,9 @@ CREATE TABLE transit.schedules (
updated_at TIMESTAMP DEFAULT NOW() updated_at TIMESTAMP DEFAULT NOW()
); );
CREATE TABLE transit.shifts ( CREATE TABLE "transit.bus".shifts (
id BIGINT PRIMARY KEY, 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) driver_id BIGINT REFERENCES workers.users(id)
); );
``` ```
@@ -246,7 +246,7 @@ The schedule manager uses:
### Create a schedule (Admin) ### Create a schedule (Admin)
```bash ```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 "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{ -d '{
@@ -261,13 +261,13 @@ curl -X POST https://transit.xtransit.testinstance.nl/admin/schedules \
### Get today's schedules (Public) ### Get today's schedules (Public)
```bash ```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) ### Get schedules for specific line (Public)
```bash ```bash
curl https://transit.xtransit.testinstance.nl/schedules?lineId=3 curl https://transit.xtransit.testinstance.nl/bus/schedules?lineId=3
``` ```
## Future Enhancements ## Future Enhancements
@@ -383,15 +383,15 @@ if (DEV_MOCK) {
## Production Database Support ## 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 ```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: Queries gebruiken:
```sql ```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 2. **Tijdzones**: Alles in UTC
- Toekomst: Voeg timezone support toe voor meerdere regio's - 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 - Status: Handmatig SQL nodig voor productie
4. **Bestuurders Toewijzing**: Geen conflict detectie 4. **Bestuurders Toewijzing**: Geen conflict detectie
+9 -4
View File
@@ -114,18 +114,23 @@ Command Center — service health:
Service Catalog — service capability: Service Catalog — service capability:
| Service | Capability | Route | Access | | Service | Capability | Route | Access |
|---------|------------|-------|--------| |---------|------------|-------|--------|
| Transit API | Daily schedules | `/schedule/daily` | Authenticated | | Transit API | Daily schedules | `/bus/schedule/daily` | Authenticated |
| Transit API | Route detail | `/routes/:routeId` | Authenticated | | Transit API | Route detail | `/bus/routes/:routeId` | Authenticated |
| Employee API | Create employee | `/createemployee` | Admin | | Employee API | Create employee | `/createemployee` | Admin |
| Error Log API | Read error log | `/error_log` | Admin | | Error Log API | Read error log | `/error_log` | Admin |
Request Lab — request preset: Request Lab — request preset:
| Service | Method | Path | Expected result | | Service | Method | Path | Expected result |
|---------|--------|------|-----------------| |---------|--------|------|-----------------|
| Transit API | GET | `/schedules/overview` | Schedule summary | | Transit API | GET | `/bus/schedules/overview` | Schedule summary |
| Transit API | GET | `/admin/drivers` | Driver roster | | Transit API | GET | `/bus/admin/drivers` | Driver roster |
| Employee API | GET | `/health` | Service health | | 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: Incident Log — normalized incident:
| Time | Service | Code | Message | Severity | | Time | Service | Code | Message | Severity |
|------|---------|------|---------|----------| |------|---------|------|---------|----------|
@@ -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.
@@ -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. 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 Schedules are assigned through `"transit.bus".shifts`; the client does not expect a
`driver_id`, `bus_id`, or `weekdays` column on `transit.schedules`. `driver_id`, `bus_id`, or `weekdays` column on `"transit.bus".schedules`.
## Build ## Build
+12
View File
@@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@vitejs/plugin-react": "5.0.0", "@vitejs/plugin-react": "5.0.0",
"marked": "^18.0.14",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"vite": "5.0.0" "vite": "5.0.0"
@@ -1225,6 +1226,17 @@
"yallist": "^3.0.2" "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": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+4 -4
View File
@@ -10,9 +10,9 @@
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-react": "5.0.0", "@vitejs/plugin-react": "5.0.0",
"vite": "5.0.0", "marked": "^18.0.14",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0" "react-dom": "18.2.0",
}, "vite": "5.0.0"
"devDependencies": {} }
} }
+2 -2
View File
@@ -79,13 +79,13 @@ const testerRoutes = [
{ {
service: "transit", service: "transit",
method: "GET", method: "GET",
path: "/public/schedules/1/progress", path: "/bus/public/schedules/1/progress",
label: "Public schedule progress", label: "Public schedule progress",
}, },
{ {
service: "transit", service: "transit",
method: "GET", method: "GET",
path: "/schedule/daily?date=2026-09-20", path: "/bus/schedule/daily?date=2026-09-20",
label: "Driver daily schedule", label: "Driver daily schedule",
}, },
{ {