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) => {
|
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}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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}`,
|
||||||
|
|||||||
@@ -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
@@ -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}`,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Generated
+12
@@ -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",
|
||||||
|
|||||||
@@ -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": {}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user