diff --git a/DOMAINS_README.md b/DOMAINS_README.md new file mode 100644 index 0000000..29afe19 --- /dev/null +++ b/DOMAINS_README.md @@ -0,0 +1,53 @@ +# XTransit Domains + +This document is the central reference for the public XTransit hostnames. +Use HTTPS for every public request. + +## Public Domains + +| Domain | Service | Purpose | +| ------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `https://auth.xtransit.testinstance.nl` | Auth API | Driver and administrator login. Issues and validates JWT authentication tokens. Use this domain only for authentication requests such as `POST /login`. | +| `https://transit.xtransit.testinstance.nl` | Transit API | Transit and schedule data used by the Windows XP driver client, admin UI, and public timetable clients. This includes assigned daily schedules, weekly templates, shifts, bus lines, stops, routes, and timetable views. | +| `https://error.xtransit.testinstance.nl` | Error Log API | Central error collection and administration. Services send errors to `POST /error_log`; administrators can inspect them through `GET /error_log`. | +| `https://admin.xtransit.testinstance.nl` | Admin panel | Browser-based administration interface for managing bus lines, stops, vehicles, weekly schedules, driver shifts, and error logs. | + +## Client Rules + +### Windows XP driver client + +The XP client must use: + +- Auth and login: `https://auth.xtransit.testinstance.nl` +- Assigned schedules and route stops: `https://transit.xtransit.testinstance.nl` + +The XP client must never use the auth domain for transit data. + +### Admin UI and public timetable + +The admin panel is available at `https://admin.xtransit.testinstance.nl`. +The admin UI and public timetable use: + +- Authentication: `https://auth.xtransit.testinstance.nl` +- Transit API: `https://transit.xtransit.testinstance.nl` +- Error logging: `https://error.xtransit.testinstance.nl` + +### Backend services + +The backend services report errors to: + +```text +https://error.xtransit.testinstance.nl/error_log +``` + +The database and internal Docker service name are deployment details and should not +be exposed as public client URLs. Public clients must use the HTTPS domains above. + +## Quick Reference + +```text +auth.xtransit.testinstance.nl Authentication and JWT tokens +transit.xtransit.testinstance.nl All transit and schedule data +error.xtransit.testinstance.nl Central error logging +admin.xtransit.testinstance.nl Browser-based administration panel +``` diff --git a/README.md b/README.md index 557e0ef..39b8918 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,15 @@ Dit repository bevat een suite van Node.js microservices, een admin webapp en ee De error API is beschikbaar via `https://error.xtransit.testinstance.nl`. - `admin_ui` — React + Vite beheerinterface + +Het admin panel is beschikbaar via `https://admin.xtransit.testinstance.nl`. + - `deprecated/` — legacy Flutter bus terminal, retained for reference while moving the client to a Windows XP-era build path Er is ook een GitHub Actions workflow aanwezig die Docker images bouwt en pusht naar GitHub Container Registry (GHCR). +Zie [DOMAINS_README.md](DOMAINS_README.md) voor de centrale lijst van alle publieke domeinen en hun functies. + Belangrijk: deze README bevat geen wachtwoorden of andere gevoelige gegevens. Sla secrets altijd buiten versiebeheer op (environment variables, secret managers, Portainer secrets, etc.). --- diff --git a/admin_ui/README.md b/admin_ui/README.md index 000c0c1..9eb9273 100644 --- a/admin_ui/README.md +++ b/admin_ui/README.md @@ -15,6 +15,8 @@ The active UI uses these transit API endpoints: # Transit Admin Control Center +Production admin panel: `https://admin.xtransit.testinstance.nl` + Modern React + Vite web application for comprehensive transit system administration. ## Features diff --git a/transit_api/README.md b/transit_api/README.md index 4a4b468..83cfcce 100644 --- a/transit_api/README.md +++ b/transit_api/README.md @@ -59,6 +59,12 @@ API draait op `https://transit.xtransit.testinstance.nl` Alle endpoints vereisen een `Authorization: Bearer {token}` header. +**GET** `/health` + +- Controleert of de gedeployde transit API draait en de database bereikbaar is. +- Geeft `200` met `{"status":"ok","database":"ok"}` bij succes. +- Geeft `503` als de API wel draait maar de database niet bereikbaar is. + ### Schedule Schedules are weekly templates. Each row in `transit.schedules` represents one diff --git a/transit_api/index.js b/transit_api/index.js index dd6faca..f90cec0 100644 --- a/transit_api/index.js +++ b/transit_api/index.js @@ -13,6 +13,14 @@ const ERROR_LOG_API_URL = process.env.ERROR_LOG_API_URL || "https://error.xtransit.testinstance.nl/error_log"; +function isValidDateKey(value) { + if (!/^\d{4}-\d{2}-\d{2}$/.test(String(value || ""))) return false; + const date = new Date(`${value}T00:00:00Z`); + return ( + !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value + ); +} + function friendlyErrorMessage(statusCode) { if (statusCode === 400) return "Controleer je invoer en probeer opnieuw."; if (statusCode === 401) return "Je sessie is verlopen. Log opnieuw in."; @@ -53,6 +61,16 @@ app.use((req, res, next) => { next(); }); +app.get("/health", async (_req, res) => { + try { + await db.query("SELECT 1"); + res.json({ status: "ok", service: "transit_api", database: "ok" }); + } catch (err) { + console.error("GET /health error:", err); + res.status(503).json({ status: "unavailable", service: "transit_api" }); + } +}); + // Development mock storage when DEV_MOCK=1 const DEV_MOCK = process.env.DEV_MOCK === "1"; const _mock = { @@ -1081,8 +1099,9 @@ app.get("/schedule/daily", authMiddleware, async (req, res) => { app.get("/driver/schedules/daily", authMiddleware, async (req, res) => { try { const { date } = req.query; - if (!date) - return res.status(400).json({ error: "date query parameter required" }); + if (!isValidDateKey(date)) { + return res.status(400).json({ error: "date must use YYYY-MM-DD format" }); + } const result = await db.query( ` @@ -1090,9 +1109,13 @@ app.get("/driver/schedules/daily", authMiddleware, async (req, res) => { bl.line_number, bl.start_stop, bl.end_stop, bl.estimated_duration_minutes, bl.description FROM transit.schedules s - JOIN transit.shifts sh ON sh.schedule_id = s.id AND sh.driver_id = $1 JOIN transit.bus_lines bl ON bl.id = s.bus_line_id - WHERE s.day_of_week = EXTRACT(DOW FROM $2::date)::int + WHERE EXISTS ( + SELECT 1 + FROM transit.shifts sh + WHERE sh.schedule_id = s.id AND sh.driver_id = $1 + ) + AND s.day_of_week = EXTRACT(DOW FROM $2::date)::int AND s.status <> 'cancelled' ORDER BY s.start_time ASC `, diff --git a/windows_xp_driver_ui/README.md b/windows_xp_driver_ui/README.md index c3609ff..d5083d0 100644 --- a/windows_xp_driver_ui/README.md +++ b/windows_xp_driver_ui/README.md @@ -2,9 +2,13 @@ Native Win32 driver client targeting Windows XP-era systems. -The client authenticates with `https://auth.xtransit.testinstance.nl`, then requests -assigned weekly schedule templates from -`https://transit.xtransit.testinstance.nl/driver/schedules/daily`. +For Windows XP compatibility, the client temporarily uses the LAN services: + +- Login: `http://192.168.68.66:5000/login` +- Assigned schedules: `http://192.168.68.66:5001/driver/schedules/daily` +- Route stops: `http://192.168.68.66:5001/bus-lines/:busLineId/stops` + +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`. @@ -25,3 +29,17 @@ makensis installer.nsi Distribute `TransitDriverXPSetup.exe`. It detects an existing installation, stops the previous client, and replaces it in the same directory. + +## Login diagnostics + +If the app shows `Driver service unreachable`, open `driver_xp.log` in the same +folder as the installed `driver_xp.exe` and inspect the latest entries. The log +records the failing stage and WinHTTP or HTTP status code, but never writes the +password or JWT token. + +Common codes: + +- `12007`: hostname could not be resolved (DNS) +- `12029`: connection to the server failed +- `12175`: HTTPS certificate or TLS validation failed +- `401` or `403`: the server responded, but authentication was rejected diff --git a/windows_xp_driver_ui/TransitDriverXPSetup.exe b/windows_xp_driver_ui/TransitDriverXPSetup.exe index c910257..9287bff 100644 Binary files a/windows_xp_driver_ui/TransitDriverXPSetup.exe and b/windows_xp_driver_ui/TransitDriverXPSetup.exe differ diff --git a/windows_xp_driver_ui/driver_xp.c b/windows_xp_driver_ui/driver_xp.c index 397f24f..db2f37d 100644 --- a/windows_xp_driver_ui/driver_xp.c +++ b/windows_xp_driver_ui/driver_xp.c @@ -25,7 +25,7 @@ #define ID_ROUTES_LABEL 116 #define MAX_BUF 65535 -#define API_HOST "auth.xtransit.testinstance.nl" +#define API_HOST "192.168.68.66" #define AUTH_PORT 5000 #define TRANSIT_PORT 5001 @@ -52,6 +52,30 @@ static void SetOutputText(HWND hwnd, const char *text) SetWindowTextA(GetDlgItem(hwnd, ID_OUTPUT), text); } +static void LogDiagnostic(const char *stage, DWORD code) +{ + char path[MAX_PATH]; + char *separator; + FILE *file; + + GetModuleFileNameA(NULL, path, sizeof(path)); + separator = strrchr(path, '\\'); + if (separator) { + *(separator + 1) = '\0'; + } else { + snprintf(path, sizeof(path), ".\\"); + } + strncat(path, "driver_xp.log", sizeof(path) - strlen(path) - 1); + + file = fopen(path, "a"); + if (!file) { + return; + } + + fprintf(file, "%s: %lu\r\n", stage, (unsigned long)code); + fclose(file); +} + static void ShowControl(HWND hwnd, int id, int visible) { ShowWindow(GetDlgItem(hwnd, id), visible ? SW_SHOW : SW_HIDE); @@ -516,8 +540,7 @@ static void ExtractTokenFromJson(const char *json, char *outToken, size_t outSiz static void HttpErrorMessage(const char *context, DWORD error) { - (void)context; - (void)error; + LogDiagnostic(context, error); } static int HttpRequestText(const char *method, @@ -690,6 +713,7 @@ static int HttpRequestText(const char *method, } if (status >= 400) { + LogDiagnostic("HTTP response status", status); snprintf(response, responseSize, "HTTP error %lu", status); ok = 0; goto cleanup; @@ -749,7 +773,7 @@ static void PerformLogin(HWND hwnd, APPSTATE *state) userCode, password); SetStatusText(hwnd, "Logging in..."); - if (!HttpRequestText("POST", "https://auth.xtransit.testinstance.nl/login", payload, NULL, response, sizeof(response))) { + if (!HttpRequestText("POST", "http://192.168.68.66:5000/login", payload, NULL, response, sizeof(response))) { SetOutputText(hwnd, "Driver service unreachable. Please try again."); SetStatusText(hwnd, "Login failed"); return; @@ -786,7 +810,7 @@ static void PerformRefresh(HWND hwnd, APPSTATE *state) if (date[0] == '\0') { snprintf(date, sizeof(date), "2026-09-17"); } - snprintf(scheduleUrl, sizeof(scheduleUrl), "https://transit.xtransit.testinstance.nl/driver/schedules/daily?date=%s", date); + snprintf(scheduleUrl, sizeof(scheduleUrl), "http://192.168.68.66:5001/driver/schedules/daily?date=%s", date); SetStatusText(hwnd, "Loading schedule..."); if (!HttpRequestText("GET", scheduleUrl, NULL, authHeader, response, sizeof(response))) { @@ -827,7 +851,7 @@ static void PerformLoadStops(HWND hwnd, APPSTATE *state) } snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s\r\n", state->token); - snprintf(stopsUrl, sizeof(stopsUrl), "https://transit.xtransit.testinstance.nl/bus-lines/%d/stops", lineId); + snprintf(stopsUrl, sizeof(stopsUrl), "http://192.168.68.66:5001/bus-lines/%d/stops", lineId); SetStatusText(hwnd, "Loading stop details..."); if (!HttpRequestText("GET", stopsUrl, NULL, authHeader, response, sizeof(response))) { diff --git a/windows_xp_driver_ui/driver_xp.exe b/windows_xp_driver_ui/driver_xp.exe index 0b42adc..d74a929 100755 Binary files a/windows_xp_driver_ui/driver_xp.exe and b/windows_xp_driver_ui/driver_xp.exe differ