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
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:
+1
-1
@@ -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}` },
|
||||
});
|
||||
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,27 @@ pool.query('SELECT 1')
|
||||
.catch(err => {
|
||||
console.error('PostgreSQL connection FAILED');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
query: (text, params) => pool.query(text, params),
|
||||
|
||||
+13
-1
@@ -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}`,
|
||||
|
||||
@@ -15,8 +15,27 @@ pool.query('SELECT 1')
|
||||
.catch(err => {
|
||||
console.error('PostgreSQL connection FAILED');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
query: (text, params) => pool.query(text, params),
|
||||
|
||||
+13
-1
@@ -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}`,
|
||||
|
||||
@@ -17,8 +17,27 @@ pool.query('SELECT 1')
|
||||
.catch(err => {
|
||||
console.error('PostgreSQL connection FAILED');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
query: (text, params) => pool.query(text, params),
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|------|---------|------|---------|----------|
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
Generated
+12
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user