From 3f0a02aa04f84930f51d7f6eb1f4cbe78d46cb9d Mon Sep 17 00:00:00 2001 From: Sven laptop Date: Fri, 18 Sep 2026 18:00:51 +0200 Subject: [PATCH] feat: add weekly schedule shifts and bulk departures --- admin_ui/README.md | 29 +- admin_ui/SCHEDULE_API.md | 146 +- admin_ui/src/components/ScheduleMaker.jsx | 100 +- .../src/components/WeeklyScheduleEditor.jsx | 268 + transit_api/README.md | 90 +- transit_api/index.js | 4848 +++++++++++------ windows_xp_driver_ui/Makefile | 12 + windows_xp_driver_ui/README.md | 27 + windows_xp_driver_ui/TransitDriverXPSetup.exe | Bin 0 -> 258619 bytes windows_xp_driver_ui/driver_xp.c | 1050 ++++ windows_xp_driver_ui/driver_xp.exe | Bin 0 -> 282465 bytes windows_xp_driver_ui/installer.nsi | 57 + 12 files changed, 4818 insertions(+), 1809 deletions(-) create mode 100644 admin_ui/src/components/WeeklyScheduleEditor.jsx create mode 100644 windows_xp_driver_ui/Makefile create mode 100644 windows_xp_driver_ui/README.md create mode 100644 windows_xp_driver_ui/TransitDriverXPSetup.exe create mode 100644 windows_xp_driver_ui/driver_xp.c create mode 100755 windows_xp_driver_ui/driver_xp.exe create mode 100644 windows_xp_driver_ui/installer.nsi diff --git a/admin_ui/README.md b/admin_ui/README.md index d034bd0..7922d7c 100644 --- a/admin_ui/README.md +++ b/admin_ui/README.md @@ -1,3 +1,18 @@ +## Weekly schedules and shifts + +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`. + +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` + # Transit Admin Control Center Modern React + Vite web application for comprehensive transit system administration. @@ -15,6 +30,7 @@ Modern React + Vite web application for comprehensive transit system administrat ## Local Development ### Prerequisites + - Node.js 18+ - npm or yarn @@ -86,9 +102,9 @@ http://localhost:5174 ### Environment Variables -| Variable | Description | Default | -|----------|-------------|---------| -| `VITE_AUTH_API` | Auth API URL (same-origin path recommended in Docker) | http://localhost:5000 | +| Variable | Description | Default | +| ------------------ | -------------------------------------------------------- | --------------------- | +| `VITE_AUTH_API` | Auth API URL (same-origin path recommended in Docker) | http://localhost:5000 | | `VITE_TRANSIT_API` | Transit API URL (same-origin path recommended in Docker) | http://localhost:5001 | ### Runtime Configuration @@ -98,6 +114,7 @@ When running in Docker, environment variables are injected at runtime via `docke ## Architecture ### Technology Stack + - **Frontend**: React 18.2 + Vite 5.0 - **HTTP Client**: Axios 1.4 - **Server**: Nginx (Alpine Linux) @@ -116,6 +133,7 @@ When running in Docker, environment variables are injected at runtime via `docke ### API Integration The app communicates with: + - **Auth API** (/login, /me) - **Transit API** (/admin/stops, /admin/bus-lines, /admin/fleet/buses) - **Error Log API** (/error_log) @@ -148,16 +166,19 @@ Users must have a `userCode` in their JWT and, for admin access, the `isAdmin` f ## Troubleshooting ### Issue: "Admin UI not loading" + - Verify container is running: `docker ps` - Check logs: `docker-compose logs admin_ui` - Clear browser cache (Ctrl+Shift+R) ### Issue: "API connection refused" + - Ensure auth_api and transit_api are running - Check environment variables are set correctly - Verify network connectivity between containers ### Issue: "Permission denied" on admin operations + - Verify JWT token includes valid `userCode` - Check `ADMIN_USER_CODES` environment variable - Ensure database user has appropriate permissions @@ -180,6 +201,7 @@ See [DEPLOYMENT.md](../DEPLOYMENT.md) for comprehensive deployment guide. ## Support For issues or questions: + 1. Check service logs: `docker-compose logs admin_ui` 2. Verify API connectivity: Visit API URLs directly 3. Check browser console for client-side errors @@ -188,4 +210,3 @@ For issues or questions: ## License Part of the XTransit project - diff --git a/admin_ui/SCHEDULE_API.md b/admin_ui/SCHEDULE_API.md index 4f9ff46..4415cbe 100644 --- a/admin_ui/SCHEDULE_API.md +++ b/admin_ui/SCHEDULE_API.md @@ -4,66 +4,149 @@ The Transit API now includes a comprehensive schedule management system with both admin and public endpoints. -### Admin Endpoints (requires authentication + admin privileges) +### Weekly Admin Endpoints (requires authentication + admin privileges) + +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 +`workers.users(id)`. + +#### Get weekly schedules -#### Get all schedules ``` -GET /admin/schedules +GET /admin/weekly-schedules Response: { schedules: [...] } ``` -#### Create a schedule +#### Get shift drivers + ``` +GET /admin/shift-drivers +Response: { drivers: [{ id, name }] } +``` + +#### Create a weekly schedule + +```` + +#### Create multiple departures + +```text +POST /admin/weekly-schedules/bulk +Body: { + busLineId: 1, + dayOfWeeks: [1, 2, 3, 4, 5], + startTimes: ["06:00", "06:30", "07:00"], + driverId: 12 +} +```` + +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 +Body: { +busLineId: 1, +dayOfWeek: 1, +startTime: "08:00", +endTime: "09:00", +driverId: 12 +} + +``` + +#### Replace a schedule's driver + +``` + +PUT /admin/weekly-schedules/:scheduleId/shift +Body: { driverId: 12 } + +``` + +Use `{ "driverId": null }` to unassign the driver. The current `shifts` table +does not contain a week/date column, so this is an active assignment rather than +historical per-week assignment. Add `week_start` to support that later. + +### Legacy Admin Endpoints + +#### Get all schedules + +``` + +GET /admin/schedules +Response: { schedules: [...] } + +``` + +#### Create a schedule + +``` + POST /admin/schedules Body: { - busLineId: number, - busId: number, - driverId: number (optional), - startTime: "2026-05-06T08:00:00", - endTime: "2026-05-06T17:00:00" +busLineId: number, +busId: number, +driverId: number (optional), +startTime: "2026-05-06T08:00:00", +endTime: "2026-05-06T17:00:00" } Response: { schedule: {...} } + ``` #### Delete a schedule + ``` + DELETE /admin/schedules/:scheduleId Response: { success: true, schedule: {...} } + ``` ### Public Endpoints (no authentication required) #### Get schedules (with optional filters) + ``` + GET /schedules?date=2026-05-06&lineId=1 Query params: - - date: YYYY-MM-DD (optional) - - lineId: number (optional) -Response: { schedules: [...] } + +- date: YYYY-MM-DD (optional) +- lineId: number (optional) + Response: { schedules: [...] } + ``` #### Get schedule detail + ``` + GET /schedules/:scheduleId Response: { schedule: {...} } + ``` #### Get stops for a schedule's line + ``` + GET /schedules/:scheduleId/stops Response: { stops: [...] } Shows all stops on the bus line with order, location, and ETA. -``` + +```` ## Admin UI Features The ScheduleMaker component provides: -1. **Service Creation Form** - - Select bus line, vehicle, date, and time - - Optional driver ID assignment - - Submit to create schedule - - Admin-only access +1. **Weekly schedule editor** + +- Select a bus line and one of weekdays, Saturday, or Sunday +- Define start and end times +- Assign drivers through shifts +- Admin-only access 2. **Timetable View** - 24-hour time slots (hourly grouping) @@ -86,12 +169,13 @@ The ScheduleMaker component provides: The TimetableViewer component provides a public-facing schedule display: ```jsx -import TimetableViewer from './components/TimetableViewer' +import TimetableViewer from "./components/TimetableViewer"; - -``` +; +```` Features: + - Filter by date and bus line - Real-time loading state - Error handling @@ -104,6 +188,7 @@ Features: With `DEV_MOCK=1`, all schedule operations work in-memory without a database. Example: + ```bash ADMIN_USER_CODES=42 DEV_MOCK=1 JWT_SECRET=devsecret npm --prefix ./transit_api start ``` @@ -114,14 +199,19 @@ ADMIN_USER_CODES=42 DEV_MOCK=1 JWT_SECRET=devsecret npm --prefix ./transit_api s CREATE TABLE transit.schedules ( id BIGINT PRIMARY KEY, bus_line_id BIGINT REFERENCES transit.bus_lines(id), - bus_id BIGINT REFERENCES fleet.bus(id), - driver_id INTEGER, - start_time TIMESTAMP, - end_time TIMESTAMP, + start_time TIME, + end_time TIME, status VARCHAR(20) DEFAULT 'planned', + day_of_week SMALLINT NOT NULL CHECK (day_of_week BETWEEN 0 AND 6), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); + +CREATE TABLE transit.shifts ( + id BIGINT PRIMARY KEY, + schedule_id BIGINT REFERENCES transit.schedules(id) ON DELETE CASCADE, + driver_id BIGINT REFERENCES workers.users(id) +); ``` ## Styling & UX @@ -129,6 +219,7 @@ CREATE TABLE transit.schedules ( The schedule manager uses: ### Admin Timetable Styling + - Responsive table with hourly time slots - Gradient schedule cards (blue/indigo theme) - Hover effects and smooth transitions @@ -137,6 +228,7 @@ The schedule manager uses: - Confirmation modals with semi-transparent overlay ### Public Timetable Styling + - Clean, simple table layout - Status badges (green for active, gray for planned) - Mobile-responsive with scrolling @@ -152,6 +244,7 @@ The schedule manager uses: ## Example Usage ### Create a schedule (Admin) + ```bash curl -X POST http://localhost:5001/admin/schedules \ -H "Authorization: Bearer YOUR_TOKEN" \ @@ -166,11 +259,13 @@ curl -X POST http://localhost:5001/admin/schedules \ ``` ### Get today's schedules (Public) + ```bash curl http://localhost:5001/schedules?date=$(date +%Y-%m-%d) ``` ### Get schedules for specific line (Public) + ```bash curl http://localhost:5001/schedules?lineId=3 ``` @@ -184,4 +279,3 @@ curl http://localhost:5001/schedules?lineId=3 - Schedule conflict detection - Integration with GPS tracking for actual vs scheduled times - Historical schedule analytics - diff --git a/admin_ui/src/components/ScheduleMaker.jsx b/admin_ui/src/components/ScheduleMaker.jsx index 565ed88..b50c7ba 100644 --- a/admin_ui/src/components/ScheduleMaker.jsx +++ b/admin_ui/src/components/ScheduleMaker.jsx @@ -1,93 +1,61 @@ -import React, { useEffect, useState } from 'react' -import { transitClient } from '../api' -import TimetableDisplay from './TimetableDisplay' -import FixedScheduleEditor from './FixedScheduleEditor' +import React, { useEffect, useState } from "react"; +import { transitClient } from "../api"; +import WeeklyScheduleEditor from "./WeeklyScheduleEditor"; -export default function ScheduleMaker({ token, user }) { - const client = transitClient(token) - const [scheduleTemplates, setScheduleTemplates] = useState([]) - const [busLines, setBusLines] = useState([]) - const [buses, setBuses] = useState([]) - const [drivers, setDrivers] = useState([]) - const [selectedDriverFilter, setSelectedDriverFilter] = useState('') - const [message, setMessage] = useState(null) - const [isAdmin] = useState(Boolean(user?.isAdmin)) - const [overviewDate, setOverviewDate] = useState(new Date().toISOString().split('T')[0]) +export default function ScheduleMaker({ token }) { + const client = transitClient(token); + const [schedules, setSchedules] = useState([]); + const [busLines, setBusLines] = useState([]); + const [drivers, setDrivers] = useState([]); + const [message, setMessage] = useState(null); - const loadScheduleTemplates = async () => { + const loadSchedules = async () => { try { - const res = await client.get('/admin/schedules?templates=1') - setScheduleTemplates((res.data.schedules || []).filter(schedule => Array.isArray(schedule.weekdays) && schedule.weekdays.length > 0)) + const res = await client.get("/admin/weekly-schedules"); + setSchedules(res.data.schedules || []); } catch (err) { - setMessage('Error loading fixed schedules: ' + (err.response?.data?.error || err.message)) + setMessage("Could not load weekly schedules."); } - } + }; const loadBusLines = async () => { try { - const res = await client.get('/admin/bus-lines') - setBusLines(res.data.busLines || []) + const res = await client.get("/admin/bus-lines"); + setBusLines(res.data.busLines || []); } catch (err) { - setMessage('Error loading bus lines: ' + (err.response?.data?.error || err.message)) + setMessage("Could not load bus lines."); } - } - - const loadBuses = async () => { - try { - const res = await client.get('/admin/fleet/buses') - setBuses(res.data.buses || []) - } catch (err) { - setMessage('Error loading buses: ' + (err.response?.data?.error || err.message)) - } - } + }; const loadDrivers = async () => { try { - const res = await client.get('/admin/drivers') - setDrivers(res.data.drivers || []) + const res = await client.get("/admin/shift-drivers"); + setDrivers(res.data.drivers || []); } catch (err) { - setMessage('Error loading drivers: ' + (err.response?.data?.error || err.message)) + setMessage("Could not load drivers."); } - } + }; useEffect(() => { - loadScheduleTemplates() - loadBusLines() - loadBuses() - loadDrivers() - }, []) + loadSchedules(); + loadBusLines(); + loadDrivers(); + }, []); const handleScheduleChange = async () => { - await loadScheduleTemplates() - } + await loadSchedules(); + }; return (
- {/* Timetable widget - compact daily view */} - - - {/* Fixed service editor - separate widget */} - {isAdmin && ( - - )} + {message &&
{message}
}
- ) + ); } diff --git a/admin_ui/src/components/WeeklyScheduleEditor.jsx b/admin_ui/src/components/WeeklyScheduleEditor.jsx new file mode 100644 index 0000000..d702cfe --- /dev/null +++ b/admin_ui/src/components/WeeklyScheduleEditor.jsx @@ -0,0 +1,268 @@ +import React, { useMemo, useState } from "react"; + +const DAY_GROUPS = [ + { value: "weekdays", label: "Weekdays", days: [1, 2, 3, 4, 5] }, + { value: "saturday", label: "Saturday", days: [6] }, + { value: "sunday", label: "Sunday", days: [0] }, +]; + +const dayLabel = (day) => + [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ][day]; + +export default function WeeklyScheduleEditor({ + client, + busLines, + drivers, + schedules, + onChange, +}) { + const [lineId, setLineId] = useState(""); + const [category, setCategory] = useState("weekdays"); + const [startTimes, setStartTimes] = useState(["08:00"]); + const [newStartTime, setNewStartTime] = useState(""); + const [driverId, setDriverId] = useState(""); + const [message, setMessage] = useState(null); + const [saving, setSaving] = useState(false); + + const groupedSchedules = useMemo(() => { + return schedules.reduce((groups, schedule) => { + const key = + schedule.dayOfWeek >= 1 && schedule.dayOfWeek <= 5 + ? "weekdays" + : schedule.dayOfWeek === 6 + ? "saturday" + : "sunday"; + groups[key] = [...(groups[key] || []), schedule]; + return groups; + }, {}); + }, [schedules]); + + const createSchedules = async (event) => { + event.preventDefault(); + setMessage(null); + if (!lineId || startTimes.length === 0) { + setMessage("Choose a line and add at least one departure time."); + return; + } + + const group = DAY_GROUPS.find((item) => item.value === category); + setSaving(true); + try { + const response = await client.post("/admin/weekly-schedules/bulk", { + busLineId: Number(lineId), + dayOfWeeks: group.days, + startTimes, + driverId: driverId ? Number(driverId) : null, + }); + setMessage( + `${response.data.createdCount} departures created for ${group.label}.`, + ); + setStartTimes(["08:00"]); + setNewStartTime(""); + onChange(); + } catch (error) { + setMessage(error.response?.data?.error || "Could not save the schedule."); + } finally { + setSaving(false); + } + }; + + const addStartTime = () => { + if (!newStartTime || startTimes.includes(newStartTime)) return; + setStartTimes([...startTimes, newStartTime].sort()); + setNewStartTime(""); + }; + + const removeStartTime = (time) => { + setStartTimes(startTimes.filter((item) => item !== time)); + }; + + const assignDriver = async (scheduleId, value) => { + try { + await client.put(`/admin/weekly-schedules/${scheduleId}/shift`, { + driverId: value ? Number(value) : null, + }); + setMessage("Driver assignment updated."); + onChange(); + } catch (error) { + setMessage( + error.response?.data?.error || + "Could not update the driver assignment.", + ); + } + }; + + const removeSchedule = async (scheduleId) => { + if (!window.confirm("Delete this weekly schedule?")) return; + try { + await client.delete(`/admin/weekly-schedules/${scheduleId}`); + setMessage("Weekly schedule deleted."); + onChange(); + } catch (error) { + setMessage( + error.response?.data?.error || "Could not delete the schedule.", + ); + } + }; + + return ( +
+
+
+

Weekly schedules

+

+ Add multiple daily departures for weekdays, Saturday, or Sunday, + then assign a driver to the generated templates. +

+
+
+ +
+
+ + +
+
+ setNewStartTime(event.target.value)} + aria-label="New departure time" + /> + +
+ {startTimes.map((time) => ( + + {time} + + + ))} +
+ + +
+
+ + {message &&
{message}
} + + {DAY_GROUPS.map((group) => ( +
+

{group.label}

+ {(groupedSchedules[group.value] || []).length === 0 ? ( +

No schedules configured.

+ ) : ( +
+ {groupedSchedules[group.value].map((schedule) => ( +
+
+ Line {schedule.lineNumber} + + {String(schedule.startTime).slice(0, 5)} -{" "} + {String(schedule.endTime).slice(0, 5)} + +
+
+ {dayLabel(schedule.dayOfWeek)}: {schedule.startStop} to{" "} + {schedule.endStop} +
+
+ + +
+
+ ))} +
+ )} +
+ ))} +
+ ); +} diff --git a/transit_api/README.md b/transit_api/README.md index 9433513..c9fe502 100644 --- a/transit_api/README.md +++ b/transit_api/README.md @@ -61,9 +61,56 @@ Alle endpoints vereisen een `Authorization: Bearer {token}` header. ### Schedule +Schedules are weekly templates. Each row in `transit.schedules` represents one +departure time for one bus line and one `day_of_week`: + +- `1` to `5`: weekdays +- `6`: Saturday +- `0`: Sunday + +Driver assignments are stored separately in `transit.shifts` with +`(schedule_id, driver_id)`, where `driver_id` references `workers.users(id)`. +The current `shifts` shape represents the active assignment. To retain different +assignments for different calendar weeks, add a `week_start` (or assignment date +range) column and include it in the unique key. + +**GET** `/driver/schedules/daily?date=2026-09-18` + +- Returns only weekly schedule templates assigned to the authenticated driver through `transit.shifts`. + +**PATCH** `/driver/weekly-schedules/:scheduleId/status` + +- Updates the status of an assigned weekly schedule. + +**GET** `/admin/weekly-schedules` + +- Lists weekly templates and their current driver assignments. + +**GET** `/admin/shift-drivers` + +- Lists drivers from `workers.users` for shift assignment. + +**POST** `/admin/weekly-schedules` + +- Creates one template. Body: `{ "busLineId": 1, "dayOfWeek": 1, "startTime": "08:00", "endTime": "09:00", "driverId": 12 }`. + +**POST** `/admin/weekly-schedules/bulk` + +- Creates multiple departures in one transaction. The API derives each end time + from the selected bus line duration. Body: `{ "busLineId": 1, "dayOfWeeks": [1,2,3,4,5], "startTimes": ["06:00", "06:30", "07:00"], "driverId": 12 }`. + +**PUT** `/admin/weekly-schedules/:scheduleId/shift` + +- Replaces the driver assignment. Pass `{ "driverId": 12 }` or `{ "driverId": null }`. + +The legacy `/admin/schedules` and `/schedule/daily` endpoints remain for older +clients, but new clients must use the weekly schedule endpoints above. + **GET** `/schedule/daily?date=2024-05-05` + - Ophalen van dagplanning voor chauffeur - Response: + ```json { "date": "2024-05-05", @@ -88,14 +135,16 @@ Alle endpoints vereisen een `Authorization: Bearer {token}` header. ``` **GET** `/routes/:routeId` + - Details van een specifieke route - Response: `{ "route": {...} }` ### Public Schedule Overview **GET** `/schedules/overview?date=2026-05-06&lineId=1` + - Haalt een datum-gebonden overzicht op van geplande diensten -- Herhaalde diensten worden naar de geselecteerde datum gematerialiseerd via `weekdays` +- Weekly templates are selected by `day_of_week` and driver assignments by `shifts` - Incidentele diensten worden alleen meegenomen op hun exacte datum - Filters: `driverId`/`chauffeurId`, `stopId`/`busStopId`, `lineId`, `status` - Response: `{ "date": "2026-05-06", "summary": {...}, "schedules": [...] }` @@ -103,6 +152,7 @@ Alle endpoints vereisen een `Authorization: Bearer {token}` header. De legacy endpoint **GET** `/schedules` blijft beschikbaar voor bestaande clients. Voorbeeld: + ```bash curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&busStopId=3" ``` @@ -110,8 +160,10 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu ### Navigation **POST** `/navigation/route` + - Opslaan van berekende route van OSRM - Body: + ```json { "routeId": 1, @@ -129,8 +181,10 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu ``` **GET** `/bus-lines/:busLineId/stops` + - Alle stops van een buslijn (voor navigatie waypoints) - Response: + ```json { "stops": [ @@ -138,8 +192,8 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu "id": 1, "order": 1, "name": "Central Station", - "latitude": 52.5200, - "longitude": 13.4050 + "latitude": 52.52, + "longitude": 13.405 } ] } @@ -148,13 +202,15 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu ### GPS Tracking **POST** `/gps/track` + - Real-time positie opslaan - Body: + ```json { "routeId": 1, - "latitude": 52.5210, - "longitude": 13.4060, + "latitude": 52.521, + "longitude": 13.406, "speed": 45.5, "heading": 180, "accuracy": 5.0 @@ -162,15 +218,17 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu ``` **GET** `/gps/route/:routeId` + - Alle opgeslagen positie punten van een route - Response: + ```json { "positions": [ { "id": 1, - "latitude": 52.5210, - "longitude": 13.4060, + "latitude": 52.521, + "longitude": 13.406, "speed": 45.5, "heading": 180, "recordedAt": "2024-05-05T08:15:00Z" @@ -182,6 +240,7 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu ### Route Status **PATCH** `/routes/:routeId/status` + - Update route status - Body: `{ "status": "in_progress" }` - Valid statuses: `scheduled`, `in_progress`, `completed`, `cancelled` @@ -191,19 +250,23 @@ curl "http://localhost:5001/schedules/overview?date=2026-05-06&chauffeurId=12&bu Alle admin-endpoints vereisen naast een geldig JWT-token ook dat je `userCode` voorkomt in `ADMIN_USER_CODES`. **POST** `/admin/stops` + - Maak een herbruikbare stop aan - Body: + ```json { "name": "Central Station", - "latitude": 52.5200, - "longitude": 13.4050 + "latitude": 52.52, + "longitude": 13.405 } ``` **POST** `/admin/bus-lines/:busLineId/stops` + - Koppel een bestaande stop aan een buslijn - Body: + ```json { "stopId": 1, @@ -215,12 +278,14 @@ Alle admin-endpoints vereisen naast een geldig JWT-token ook dat je `userCode` v ## Database Schema ### bus_types + - `id` - Primary key - `name` - Bus type name (Standard Bus, etc.) - `seat_capacity` - Number of seats - `license_plate` - Unique license plate ### bus_lines + - `id` - Primary key - `line_number` - Unique line number (1, 5, 12, etc.) - `start_stop` - Start stop name @@ -228,11 +293,13 @@ Alle admin-endpoints vereisen naast een geldig JWT-token ook dat je `userCode` v - `estimated_duration_minutes` - Expected route duration ### stops + - `id` - Primary key - `name` - Stop name - `latitude`, `longitude` - Stop location ### route_stops + - `id` - Primary key - `bus_line_id` - Which bus line the stop belongs to - `stop_id` - Reusable stop reference @@ -240,6 +307,7 @@ Alle admin-endpoints vereisen naast een geldig JWT-token ook dat je `userCode` v - `estimated_arrival_minutes` - Optional ETA per stop ### routes + - `id` - Primary key - `user_id` - Chauffeur (references workers.users) - `bus_line_id` - Which line (references bus_lines) @@ -248,6 +316,7 @@ Alle admin-endpoints vereisen naast een geldig JWT-token ook dat je `userCode` v - `status` - scheduled/in_progress/completed/cancelled ### navigation_routes + - `id` - Primary key - `route_id` - Link to route (unique) - `polyline_geojson` - GeoJSON LineString van de route @@ -255,6 +324,7 @@ Alle admin-endpoints vereisen naast een geldig JWT-token ook dat je `userCode` v - `total_duration_seconds` - Estimated duration ### gps_tracking + - `id` - Primary key - `route_id` - Which route - `latitude`, `longitude` - Current position @@ -287,6 +357,7 @@ await scheduleService.updateRouteStatus(1, 'in_progress'); ## Error Handling Alle endpoints retourneren error responses: + ```json { "error": "error_code", @@ -295,6 +366,7 @@ Alle endpoints retourneren error responses: ``` Common errors: + - `missing_token` - No Authorization header - `invalid_token` - Token verification failed - `missing_required_fields` - Required data missing diff --git a/transit_api/index.js b/transit_api/index.js index c1ead49..6dff590 100644 --- a/transit_api/index.js +++ b/transit_api/index.js @@ -1,31 +1,32 @@ -const express = require('express'); -const cors = require('cors'); -require('dotenv').config(); +const express = require("express"); +const cors = require("cors"); +require("dotenv").config(); -const db = require('./db'); -const { authMiddleware } = require('./middleware'); +const db = require("./db"); +const { authMiddleware } = require("./middleware"); const app = express(); app.use(express.json()); app.use(cors()); -const ERROR_LOG_API_URL = process.env.ERROR_LOG_API_URL || 'http://localhost:5003/error_log'; +const ERROR_LOG_API_URL = + process.env.ERROR_LOG_API_URL || "http://localhost:5003/error_log"; function friendlyErrorMessage(statusCode) { - if (statusCode === 400) return 'Controleer je invoer en probeer opnieuw.'; - if (statusCode === 401) return 'Je sessie is verlopen. Log opnieuw in.'; - if (statusCode === 403) return 'Je hebt hier geen toegang toe.'; - if (statusCode === 404) return 'Niet gevonden.'; - return 'Er ging iets mis. Probeer het later opnieuw.'; + if (statusCode === 400) return "Controleer je invoer en probeer opnieuw."; + if (statusCode === 401) return "Je sessie is verlopen. Log opnieuw in."; + if (statusCode === 403) return "Je hebt hier geen toegang toe."; + if (statusCode === 404) return "Niet gevonden."; + return "Er ging iets mis. Probeer het later opnieuw."; } async function reportError({ partOfService, error }) { try { await fetch(ERROR_LOG_API_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - service: 'transit_api', + service: "transit_api", partOfService, error, }), @@ -36,9 +37,13 @@ async function reportError({ partOfService, error }) { 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}`; - void reportError({ partOfService: `${req.method} ${req.originalUrl}`, error: rawError }); + if (res.statusCode >= 400 && body && typeof body === "object") { + const rawError = + body.error || body.message || body.details || `HTTP ${res.statusCode}`; + void reportError({ + partOfService: `${req.method} ${req.originalUrl}`, + error: rawError, + }); return originalJson({ error: friendlyErrorMessage(res.statusCode) }); } return originalJson(body); @@ -48,7 +53,7 @@ app.use((req, res, next) => { }); // Development mock storage when DEV_MOCK=1 -const DEV_MOCK = process.env.DEV_MOCK === '1'; +const DEV_MOCK = process.env.DEV_MOCK === "1"; const _mock = { stops: [], routeStops: [], @@ -69,40 +74,140 @@ const _mock = { // Initialize mock drivers if (DEV_MOCK) { _mock.drivers = [ - { id: 1, name: 'Jan Peterse', email: 'jan@transit.local', phone: '06-12345678', status: 'active' }, - { id: 2, name: 'Maria Kuijpers', email: 'maria@transit.local', phone: '06-87654321', status: 'active' }, - { id: 3, name: 'Robert Smits', email: 'robert@transit.local', phone: '06-55555555', status: 'active' }, - { id: 4, name: 'Anja Voorn', email: 'anja@transit.local', phone: '06-44444444', status: 'inactive' }, + { + id: 1, + name: "Jan Peterse", + email: "jan@transit.local", + phone: "06-12345678", + status: "active", + }, + { + id: 2, + name: "Maria Kuijpers", + email: "maria@transit.local", + phone: "06-87654321", + status: "active", + }, + { + id: 3, + name: "Robert Smits", + email: "robert@transit.local", + phone: "06-55555555", + status: "active", + }, + { + id: 4, + name: "Anja Voorn", + email: "anja@transit.local", + phone: "06-44444444", + status: "inactive", + }, ]; _mock.nextDriverId = 5; // Initialize test bus lines _mock.busLines = [ - { id: 1, lineNumber: 1, startStop: 'Central Station', endStop: 'Airport', estimatedDurationMinutes: 45, description: 'Main route' }, - { id: 2, lineNumber: 2, startStop: 'Central Station', endStop: 'Harbor', estimatedDurationMinutes: 30, description: 'Harbor route' }, + { + id: 1, + lineNumber: 1, + startStop: "Central Station", + endStop: "Airport", + estimatedDurationMinutes: 45, + description: "Main route", + }, + { + id: 2, + lineNumber: 2, + startStop: "Central Station", + endStop: "Harbor", + estimatedDurationMinutes: 30, + description: "Harbor route", + }, ]; _mock.nextBusLineId = 3; // Initialize test stops _mock.stops = [ - { id: 1, name: 'Central Station', latitude: 52.0116, longitude: 4.7055, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, - { id: 2, name: 'City Center', latitude: 52.0125, longitude: 4.7100, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, - { id: 3, name: 'Airport', latitude: 52.0300, longitude: 4.7500, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { + id: 1, + name: "Central Station", + latitude: 52.0116, + longitude: 4.7055, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + { + id: 2, + name: "City Center", + latitude: 52.0125, + longitude: 4.71, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + { + id: 3, + name: "Airport", + latitude: 52.03, + longitude: 4.75, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, ]; _mock.nextStopId = 4; // Initialize test route stops _mock.routeStops = [ - { id: 1, busLineId: 1, stopId: 1, stopOrder: 1, estimatedArrivalMinutes: 0, name: 'Central Station', latitude: 52.0116, longitude: 4.7055 }, - { id: 2, busLineId: 1, stopId: 2, stopOrder: 2, estimatedArrivalMinutes: 15, name: 'City Center', latitude: 52.0125, longitude: 4.7100 }, - { id: 3, busLineId: 1, stopId: 3, stopOrder: 3, estimatedArrivalMinutes: 45, name: 'Airport', latitude: 52.0300, longitude: 4.7500 }, + { + id: 1, + busLineId: 1, + stopId: 1, + stopOrder: 1, + estimatedArrivalMinutes: 0, + name: "Central Station", + latitude: 52.0116, + longitude: 4.7055, + }, + { + id: 2, + busLineId: 1, + stopId: 2, + stopOrder: 2, + estimatedArrivalMinutes: 15, + name: "City Center", + latitude: 52.0125, + longitude: 4.71, + }, + { + id: 3, + busLineId: 1, + stopId: 3, + stopOrder: 3, + estimatedArrivalMinutes: 45, + name: "Airport", + latitude: 52.03, + longitude: 4.75, + }, ]; _mock.nextRouteStopId = 4; // Initialize test fleet buses _mock.fleetBuses = [ - { id: 1, name: 'Bus 101', seatCapacity: 45, licensePlate: 'AB-01-CD', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, - { id: 2, name: 'Bus 102', seatCapacity: 50, licensePlate: 'AB-02-CD', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, + { + id: 1, + name: "Bus 101", + seatCapacity: 45, + licensePlate: "AB-01-CD", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + { + id: 2, + name: "Bus 102", + seatCapacity: 50, + licensePlate: "AB-02-CD", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, ]; _mock.nextFleetBusId = 3; @@ -121,7 +226,7 @@ if (DEV_MOCK) { startTime: tomorrow.toISOString(), endTime: endTime.toISOString(), weekdays: [], - status: 'planned', + status: "planned", createdAt: new Date().toISOString(), }, ]; @@ -130,7 +235,7 @@ if (DEV_MOCK) { const adminMiddleware = (req, res, next) => { if (!req.user || !req.user.isAdmin) { - return res.status(403).json({ error: 'forbidden' }); + return res.status(403).json({ error: "forbidden" }); } next(); @@ -146,22 +251,24 @@ const adminMiddleware = (req, res, next) => { */ function calculateDepartureTimes(startTime, busLineId, mock) { const startDate = new Date(startTime); - + // Get route stops for this bus line const routeStops = mock.routeStops - .filter(rs => rs.busLineId === busLineId) + .filter((rs) => rs.busLineId === busLineId) .sort((a, b) => a.stopOrder - b.stopOrder); - return routeStops.map(rs => { + return routeStops.map((rs) => { const departureDate = new Date(startDate); // Add estimated arrival minutes to get departure time - departureDate.setMinutes(departureDate.getMinutes() + (rs.estimatedArrivalMinutes || 0)); - - const stop = mock.stops.find(s => s.id === rs.stopId); + departureDate.setMinutes( + departureDate.getMinutes() + (rs.estimatedArrivalMinutes || 0), + ); + + const stop = mock.stops.find((s) => s.id === rs.stopId); return { order: rs.stopOrder, stopId: rs.stopId, - stopName: stop?.name || 'Unknown Stop', + stopName: stop?.name || "Unknown Stop", estimatedArrivalMinutes: rs.estimatedArrivalMinutes || 0, departureTime: departureDate.toISOString(), }; @@ -170,34 +277,35 @@ function calculateDepartureTimes(startTime, busLineId, mock) { function parseWeekdaysValue(value) { if (!value) return []; - if (Array.isArray(value)) return value.map(v => Number(v)).filter(v => Number.isFinite(v)); + if (Array.isArray(value)) + return value.map((v) => Number(v)).filter((v) => Number.isFinite(v)); - if (typeof value === 'string') { + if (typeof value === "string") { const trimmed = value.trim(); if (!trimmed) return []; try { const parsed = JSON.parse(trimmed); if (Array.isArray(parsed)) { - return parsed.map(v => Number(v)).filter(v => Number.isFinite(v)); + return parsed.map((v) => Number(v)).filter((v) => Number.isFinite(v)); } } catch (_) { // Fall through to CSV/PG-array parsing. } // Handles values like "1,2,3" or "{1,2,3}". - const normalized = trimmed.replace('{', '').replace('}', ''); + const normalized = trimmed.replace("{", "").replace("}", ""); return normalized - .split(',') - .map(v => Number(v.trim())) - .filter(v => Number.isFinite(v)); + .split(",") + .map((v) => Number(v.trim())) + .filter((v) => Number.isFinite(v)); } return []; } function toNumberOrNull(value) { - if (value === undefined || value === null || value === '') return null; + if (value === undefined || value === null || value === "") return null; const numberValue = Number(value); return Number.isFinite(numberValue) ? numberValue : null; } @@ -205,12 +313,12 @@ function toNumberOrNull(value) { function parseCommaSeparatedValues(value) { if (!value && value !== 0) return []; if (Array.isArray(value)) { - return value.map(item => String(item).trim()).filter(Boolean); + return value.map((item) => String(item).trim()).filter(Boolean); } return String(value) - .split(',') - .map(item => item.trim()) + .split(",") + .map((item) => item.trim()) .filter(Boolean); } @@ -240,7 +348,7 @@ function expandRecurringScheduleRows(rows) { const templateDayOfWeek = templateDate.getDay(); const sortedWeekdays = [...new Set(weekdays)].sort((a, b) => a - b); const durationMinutes = Number( - row.estimated_duration_minutes ?? row.estimatedDurationMinutes ?? 60 + row.estimated_duration_minutes ?? row.estimatedDurationMinutes ?? 60, ); for (let week = 0; week < 52; week++) { @@ -279,7 +387,10 @@ function mapAdminScheduleRow(row, routeStopsByLine = new Map()) { id: Number(row.id), busLineId, busId: Number(row.busId ?? row.bus_id), - driverId: row.driverId !== null && row.driverId !== undefined ? Number(row.driverId) : null, + driverId: + row.driverId !== null && row.driverId !== undefined + ? Number(row.driverId) + : null, driverName: row.driverName ?? row.driver_name ?? null, startTime, endTime: row.endTime ?? row.end_time, @@ -287,7 +398,7 @@ function mapAdminScheduleRow(row, routeStopsByLine = new Map()) { departureTimes, status: row.status, lineNumber: Number(row.lineNumber ?? row.line_number) || null, - busName: row.busName ?? row.bus_name ?? 'Unknown Bus', + busName: row.busName ?? row.bus_name ?? "Unknown Bus", licensePlate: row.licensePlate ?? row.license_plate ?? null, createdAt: row.createdAt ?? row.created_at ?? null, isRecurringInstance: row.is_recurring_instance || false, @@ -297,13 +408,17 @@ function mapAdminScheduleRow(row, routeStopsByLine = new Map()) { }; } -function scheduleMatchesFilters(schedule, filters, routeStopsByLine = new Map()) { +function scheduleMatchesFilters( + schedule, + filters, + routeStopsByLine = new Map(), +) { if (!schedule || !filters) return true; const scheduleBusLineId = Number(schedule.bus_line_id ?? schedule.busLineId); const scheduleBusId = Number(schedule.bus_id ?? schedule.busId); const scheduleDriverId = Number(schedule.driver_id ?? schedule.driverId); - const scheduleStatus = String(schedule.status || ''); + const scheduleStatus = String(schedule.status || ""); const scheduleDateKey = toDateKey(schedule.start_time ?? schedule.startTime); if (filters.busLineId !== null && scheduleBusLineId !== filters.busLineId) { @@ -318,7 +433,10 @@ function scheduleMatchesFilters(schedule, filters, routeStopsByLine = new Map()) return false; } - if (filters.statuses.length > 0 && !filters.statuses.includes(scheduleStatus)) { + if ( + filters.statuses.length > 0 && + !filters.statuses.includes(scheduleStatus) + ) { return false; } @@ -326,7 +444,11 @@ function scheduleMatchesFilters(schedule, filters, routeStopsByLine = new Map()) return false; } - if (filters.fromDate && scheduleDateKey && scheduleDateKey < filters.fromDate) { + if ( + filters.fromDate && + scheduleDateKey && + scheduleDateKey < filters.fromDate + ) { return false; } @@ -336,7 +458,10 @@ function scheduleMatchesFilters(schedule, filters, routeStopsByLine = new Map()) if (filters.stopId !== null) { const routeStops = routeStopsByLine.get(scheduleBusLineId) || []; - const matchesStop = routeStops.some(routeStop => Number(routeStop.stop_id ?? routeStop.stopId) === filters.stopId); + const matchesStop = routeStops.some( + (routeStop) => + Number(routeStop.stop_id ?? routeStop.stopId) === filters.stopId, + ); if (!matchesStop) { return false; } @@ -346,50 +471,65 @@ function scheduleMatchesFilters(schedule, filters, routeStopsByLine = new Map()) } function parseScheduleFilters(query, options = {}) { - const statuses = parseCommaSeparatedValues(query.status || query.statuses || options.statuses || []); + const statuses = parseCommaSeparatedValues( + query.status || query.statuses || options.statuses || [], + ); return { date: query.date ? String(query.date).trim() : null, fromDate: query.fromDate ? String(query.fromDate).trim() : null, toDate: query.toDate ? String(query.toDate).trim() : null, - driverId: toNumberOrNull(query.driverId ?? query.driver_id ?? query.chauffeurId ?? query.chauffeur_id), + driverId: toNumberOrNull( + query.driverId ?? + query.driver_id ?? + query.chauffeurId ?? + query.chauffeur_id, + ), busId: toNumberOrNull(query.busId ?? query.bus_id), - busLineId: toNumberOrNull(query.busLineId ?? query.lineId ?? query.bus_line_id), - stopId: toNumberOrNull(query.stopId ?? query.stop_id ?? query.busStopId ?? query.bus_stop_id), + busLineId: toNumberOrNull( + query.busLineId ?? query.lineId ?? query.bus_line_id, + ), + stopId: toNumberOrNull( + query.stopId ?? query.stop_id ?? query.busStopId ?? query.bus_stop_id, + ), statuses, }; } function formatWeekdayLabel(weekdays) { - const labels = ['Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za']; + const labels = ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za"]; if (!Array.isArray(weekdays) || weekdays.length === 0) { - return 'Eenmalig'; + return "Eenmalig"; } if (weekdays.length === 7) { - return 'Dagelijks'; + return "Dagelijks"; } return [...new Set(weekdays)] .sort((a, b) => a - b) - .filter(day => day >= 0 && day < labels.length) - .map(day => labels[day]) - .join(', '); + .filter((day) => day >= 0 && day < labels.length) + .map((day) => labels[day]) + .join(", "); } function buildDepartureTimes(startTime, routeStops = []) { const startDate = new Date(startTime); - return routeStops.map(routeStop => { + return routeStops.map((routeStop) => { const departureDate = new Date(startDate); - const etaMinutes = Number(routeStop.estimated_arrival_minutes ?? routeStop.estimatedArrivalMinutes) || 0; + const etaMinutes = + Number( + routeStop.estimated_arrival_minutes ?? + routeStop.estimatedArrivalMinutes, + ) || 0; departureDate.setMinutes(departureDate.getMinutes() + etaMinutes); return { order: Number(routeStop.stop_order ?? routeStop.stopOrder), stopId: Number(routeStop.stop_id ?? routeStop.stopId), - stopName: routeStop.stop_name || routeStop.name || 'Unknown Stop', + stopName: routeStop.stop_name || routeStop.name || "Unknown Stop", estimatedArrivalMinutes: etaMinutes, departureTime: departureDate.toISOString(), }; @@ -422,7 +562,11 @@ function scheduleOccursOnDate(schedule, dateKey) { return weekdays.includes(targetDate.getDay()); } -function materializeScheduleOverviewRow(row, dateKey, routeStopsByLine = new Map()) { +function materializeScheduleOverviewRow( + row, + dateKey, + routeStopsByLine = new Map(), +) { const weekdays = parseWeekdaysValue(row.weekdays); const recurring = weekdays.length > 0; const templateStart = new Date(row.startTime ?? row.start_time); @@ -436,15 +580,20 @@ function materializeScheduleOverviewRow(row, dateKey, routeStopsByLine = new Map templateStart.getHours(), templateStart.getMinutes(), templateStart.getSeconds(), - templateStart.getMilliseconds() + templateStart.getMilliseconds(), ); startTime = occurrenceStart.toISOString(); - if (Number.isFinite(templateEnd.getTime()) && Number.isFinite(templateStart.getTime())) { + if ( + Number.isFinite(templateEnd.getTime()) && + Number.isFinite(templateStart.getTime()) + ) { const durationMillis = templateEnd.getTime() - templateStart.getTime(); const occurrenceEnd = new Date(occurrenceStart); - occurrenceEnd.setMilliseconds(occurrenceEnd.getMilliseconds() + durationMillis); + occurrenceEnd.setMilliseconds( + occurrenceEnd.getMilliseconds() + durationMillis, + ); endTime = occurrenceEnd.toISOString(); } } @@ -465,10 +614,10 @@ function materializeScheduleOverviewRow(row, dateKey, routeStopsByLine = new Map weekdays, status: row.status, lineNumber: Number(row.lineNumber ?? row.line_number) || null, - busName: row.busName ?? row.bus_name ?? 'Unknown Bus', + busName: row.busName ?? row.bus_name ?? "Unknown Bus", licensePlate: row.licensePlate ?? row.license_plate ?? null, createdAt: row.createdAt ?? row.created_at ?? null, - scheduleType: recurring ? 'recurring' : 'one_time', + scheduleType: recurring ? "recurring" : "one_time", recurrenceLabel: formatWeekdayLabel(weekdays), isRecurringInstance: recurring && Boolean(dateKey), templateId: row.templateId ?? row.template_id ?? null, @@ -478,12 +627,19 @@ function materializeScheduleOverviewRow(row, dateKey, routeStopsByLine = new Map function mapRecurringOccurrenceRows(rows, dateKey, routeStopsByLine) { return rows - .filter(row => scheduleOccursOnDate(row, dateKey)) - .map(row => materializeScheduleOverviewRow(row, dateKey, routeStopsByLine)) + .filter((row) => scheduleOccursOnDate(row, dateKey)) + .map((row) => + materializeScheduleOverviewRow(row, dateKey, routeStopsByLine), + ) .sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); } -function materializeScheduleRowsInRange(rows, fromDateKey, toDateKey, routeStopsByLine) { +function materializeScheduleRowsInRange( + rows, + fromDateKey, + toDateKey, + routeStopsByLine, +) { const occurrences = []; if (!fromDateKey || !toDateKey) return occurrences; @@ -501,7 +657,9 @@ function materializeScheduleRowsInRange(rows, fromDateKey, toDateKey, routeStops if (!weekdays || weekdays.length === 0) { const rowDateKey = toDateKey(row.start_time ?? row.startTime); if (rowDateKey && rowDateKey >= fromDateKey && rowDateKey <= toDateKey) { - occurrences.push(materializeScheduleOverviewRow(row, rowDateKey, routeStopsByLine)); + occurrences.push( + materializeScheduleOverviewRow(row, rowDateKey, routeStopsByLine), + ); } continue; } @@ -518,103 +676,152 @@ function materializeScheduleRowsInRange(rows, fromDateKey, toDateKey, routeStops // ensure cursor >= templateStart (so recurrence doesn't start before template) if (cursor < templateStart) { // advance by weeks until >= templateStart - const diffDays = Math.ceil((templateStart - cursor) / (24 * 60 * 60 * 1000)); + const diffDays = Math.ceil( + (templateStart - cursor) / (24 * 60 * 60 * 1000), + ); const weeksToAdvance = Math.ceil(diffDays / 7); cursor.setDate(cursor.getDate() + weeksToAdvance * 7); } while (cursor <= toDate) { const occurrenceKey = cursor.toISOString().slice(0, 10); - occurrences.push(materializeScheduleOverviewRow(row, occurrenceKey, routeStopsByLine)); + occurrences.push( + materializeScheduleOverviewRow(row, occurrenceKey, routeStopsByLine), + ); cursor.setDate(cursor.getDate() + 7); } } } // sort and return - return occurrences.sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); + return occurrences.sort( + (a, b) => new Date(a.startTime) - new Date(b.startTime), + ); } /** * Check if two schedules have date/weekday overlap */ -function doSchedulesDateOverlap(schedule1Start, schedule1End, schedule1Weekdays, schedule2Start, schedule2End, schedule2Weekdays) { +function doSchedulesDateOverlap( + schedule1Start, + schedule1End, + schedule1Weekdays, + schedule2Start, + schedule2End, + schedule2Weekdays, +) { const s1Start = new Date(schedule1Start); const s1End = new Date(schedule1End); const s2Start = new Date(schedule2Start); const s2End = new Date(schedule2End); - + // Check time overlap if (s1End <= s2Start || s2End <= s1Start) { return false; // No time overlap } - + // If both have no weekdays (one-time), check if same day - if ((!schedule1Weekdays || schedule1Weekdays.length === 0) && - (!schedule2Weekdays || schedule2Weekdays.length === 0)) { + if ( + (!schedule1Weekdays || schedule1Weekdays.length === 0) && + (!schedule2Weekdays || schedule2Weekdays.length === 0) + ) { return s1Start.toDateString() === s2Start.toDateString(); } - + // If one has weekdays and other doesn't, check if one-time falls on weekday - if (schedule1Weekdays && schedule1Weekdays.length > 0 && - (!schedule2Weekdays || schedule2Weekdays.length === 0)) { + if ( + schedule1Weekdays && + schedule1Weekdays.length > 0 && + (!schedule2Weekdays || schedule2Weekdays.length === 0) + ) { const s2Day = s2Start.getDay(); // 0=Sun, 1=Mon, etc. const s2Adjusted = s2Day === 0 ? 0 : s2Day; // Normalize to 0-6 return schedule1Weekdays.includes(s2Adjusted); } - - if ((!schedule1Weekdays || schedule1Weekdays.length === 0) && - schedule2Weekdays && schedule2Weekdays.length > 0) { + + if ( + (!schedule1Weekdays || schedule1Weekdays.length === 0) && + schedule2Weekdays && + schedule2Weekdays.length > 0 + ) { const s1Day = s1Start.getDay(); const s1Adjusted = s1Day === 0 ? 0 : s1Day; return schedule2Weekdays.includes(s1Adjusted); } - + // Both have weekdays: check if any day overlaps if (schedule1Weekdays && schedule2Weekdays) { - return schedule1Weekdays.some(d => schedule2Weekdays.includes(d)); + return schedule1Weekdays.some((d) => schedule2Weekdays.includes(d)); } - + return false; } /** * Check if a driver has conflicts with existing schedules */ -function checkDriverConflict(driverId, startTime, endTime, weekdays, allSchedules, excludeScheduleId = null) { +function checkDriverConflict( + driverId, + startTime, + endTime, + weekdays, + allSchedules, + excludeScheduleId = null, +) { if (!driverId) return false; // No conflict if no driver specified - - return allSchedules.some(s => { + + return allSchedules.some((s) => { if (excludeScheduleId && s.id === excludeScheduleId) return false; const sDriverId = s.driverId ?? s.driver_id; if (Number(sDriverId) !== Number(driverId)) return false; - + const sWeekdays = parseWeekdaysValue(s.weekdays); const wkdays = weekdays || []; const sStart = s.startTime ?? s.start_time; const sEnd = s.endTime ?? s.end_time; - - return doSchedulesDateOverlap(startTime, endTime, wkdays, sStart, sEnd, sWeekdays); + + return doSchedulesDateOverlap( + startTime, + endTime, + wkdays, + sStart, + sEnd, + sWeekdays, + ); }); } /** * Check if a bus has conflicts with existing schedules */ -function checkBusConflict(busId, startTime, endTime, weekdays, allSchedules, excludeScheduleId = null) { +function checkBusConflict( + busId, + startTime, + endTime, + weekdays, + allSchedules, + excludeScheduleId = null, +) { if (!busId) return false; - - return allSchedules.some(s => { + + return allSchedules.some((s) => { if (excludeScheduleId && s.id === excludeScheduleId) return false; const sBusId = s.busId ?? s.bus_id; if (Number(sBusId) !== Number(busId)) return false; - + const sWeekdays = parseWeekdaysValue(s.weekdays); const wkdays = weekdays || []; const sStart = s.startTime ?? s.start_time; const sEnd = s.endTime ?? s.end_time; - - return doSchedulesDateOverlap(startTime, endTime, wkdays, sStart, sEnd, sWeekdays); + + return doSchedulesDateOverlap( + startTime, + endTime, + wkdays, + sStart, + sEnd, + sWeekdays, + ); }); } @@ -695,16 +902,17 @@ async function ensureScheduleStopProgressTable() { // ============================================ // ROUTES - Get daily schedule // ============================================ -app.get('/schedule/daily', authMiddleware, async (req, res) => { +app.get("/schedule/daily", authMiddleware, async (req, res) => { try { const { date } = req.query; const userId = req.user.userId; if (!date) { - return res.status(400).json({ error: 'date query parameter required' }); + return res.status(400).json({ error: "date query parameter required" }); } - const result = await db.query(` + const result = await db.query( + ` SELECT r.id, r.start_time, @@ -725,9 +933,11 @@ app.get('/schedule/daily', authMiddleware, async (req, res) => { JOIN fleet.bus bt ON r.bus_type_id = bt.id WHERE r.user_id = $1 AND DATE(r.start_time) = $2 ORDER BY r.start_time ASC - `, [userId, date]); + `, + [userId, date], + ); - let routes = result.rows.map(row => ({ + let routes = result.rows.map((row) => ({ id: row.id, startTime: row.start_time, endTime: row.end_time, @@ -748,12 +958,13 @@ app.get('/schedule/daily', authMiddleware, async (req, res) => { }, })); - // If no explicit "routes" rows exist for this user/date, fall back to - // checking scheduled runs in `transit.schedules` (filtered by driver_id). - // This works around cases where DB permissions prevent inserting into - // `transit.routes` but schedules exist and should be shown to the driver. - if (!DEV_MOCK && routes.length === 0) { - const fallback = await db.query(` + // If no explicit "routes" rows exist for this user/date, fall back to + // checking scheduled runs in `transit.schedules` (filtered by driver_id). + // This works around cases where DB permissions prevent inserting into + // `transit.routes` but schedules exist and should be shown to the driver. + if (!DEV_MOCK && routes.length === 0) { + const fallback = await db.query( + ` SELECT s.id, s.start_time, @@ -775,94 +986,152 @@ app.get('/schedule/daily', authMiddleware, async (req, res) => { JOIN fleet.bus bt ON s.bus_id = bt.id WHERE s.driver_id = $1 AND DATE(s.start_time) = $2 ORDER BY s.start_time ASC - `, [userId, date]); + `, + [userId, date], + ); - routes = fallback.rows.map(row => ({ - id: row.id, - startTime: row.start_time, - endTime: row.end_time, - status: row.status, - busLine: { - id: row.bus_line_id, - lineNumber: row.line_number, - startStop: row.start_stop, - endStop: row.end_stop, - estimatedDuration: row.estimated_duration_minutes, - description: row.description, - }, - busType: { - id: row.bus_type_id, - name: row.bus_type, - seatCapacity: row.seat_capacity, - licensePlate: row.license_plate, - }, - })); - } + routes = fallback.rows.map((row) => ({ + id: row.id, + startTime: row.start_time, + endTime: row.end_time, + status: row.status, + busLine: { + id: row.bus_line_id, + lineNumber: row.line_number, + startStop: row.start_stop, + endStop: row.end_stop, + estimatedDuration: row.estimated_duration_minutes, + description: row.description, + }, + busType: { + id: row.bus_type_id, + name: row.bus_type, + seatCapacity: row.seat_capacity, + licensePlate: row.license_plate, + }, + })); + } - // If still empty, try to synthesize a single route by shifting a recent - // schedule's time-of-day to the requested date. This is a non-destructive - // fallback to make sure drivers see a trip when DB inserts are restricted. - if (!DEV_MOCK && routes.length === 0) { - const tmplRes = await db.query(` + // If still empty, try to synthesize a single route by shifting a recent + // schedule's time-of-day to the requested date. This is a non-destructive + // fallback to make sure drivers see a trip when DB inserts are restricted. + if (!DEV_MOCK && routes.length === 0) { + const tmplRes = await db.query( + ` SELECT s.start_time, s.end_time, s.status, s.bus_line_id, s.bus_id, s.driver_id, bl.estimated_duration_minutes, bl.line_number, bl.start_stop, bl.end_stop, bl.description FROM transit.schedules s JOIN transit.bus_lines bl ON s.bus_line_id = bl.id WHERE s.driver_id = $1 ORDER BY s.start_time DESC LIMIT 1 - `, [userId]); + `, + [userId], + ); - if (tmplRes.rowCount > 0) { - const t = tmplRes.rows[0]; - const requestedDate = new Date(date + 'T00:00:00.000Z'); - const srcStart = new Date(t.start_time); - const srcEnd = new Date(t.end_time); + if (tmplRes.rowCount > 0) { + const t = tmplRes.rows[0]; + const requestedDate = new Date(date + "T00:00:00.000Z"); + const srcStart = new Date(t.start_time); + const srcEnd = new Date(t.end_time); - // Preserve time-of-day from template, apply to requested date - const newStart = new Date(requestedDate); - newStart.setUTCHours(srcStart.getUTCHours(), srcStart.getUTCMinutes(), srcStart.getUTCSeconds(), srcStart.getUTCMilliseconds()); - const durationMs = srcEnd.getTime() - srcStart.getTime(); - const newEnd = new Date(newStart.getTime() + durationMs); + // Preserve time-of-day from template, apply to requested date + const newStart = new Date(requestedDate); + newStart.setUTCHours( + srcStart.getUTCHours(), + srcStart.getUTCMinutes(), + srcStart.getUTCSeconds(), + srcStart.getUTCMilliseconds(), + ); + const durationMs = srcEnd.getTime() - srcStart.getTime(); + const newEnd = new Date(newStart.getTime() + durationMs); - routes.push({ - id: null, - startTime: newStart.toISOString(), - endTime: newEnd.toISOString(), - status: t.status || 'planned', - busLine: { - id: t.bus_line_id, - lineNumber: t.line_number, - startStop: t.start_stop, - endStop: t.end_stop, - estimatedDuration: t.estimated_duration_minutes, - description: t.description, - }, - busType: { - id: t.bus_id, - name: null, - seatCapacity: null, - licensePlate: null, - }, - }); - } + routes.push({ + id: null, + startTime: newStart.toISOString(), + endTime: newEnd.toISOString(), + status: t.status || "planned", + busLine: { + id: t.bus_line_id, + lineNumber: t.line_number, + startStop: t.start_stop, + endStop: t.end_stop, + estimatedDuration: t.estimated_duration_minutes, + description: t.description, + }, + busType: { + id: t.bus_id, + name: null, + seatCapacity: null, + licensePlate: null, + }, + }); } + } res.json({ date, routes }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); + } +}); + +// DRIVER - Weekly schedule templates assigned through transit.shifts. +// day_of_week uses JavaScript/PostgreSQL convention: 0 Sunday, 1 Monday ... 6 Saturday. +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" }); + + const result = await db.query( + ` + SELECT s.id, s.bus_line_id, s.start_time, s.end_time, s.status, s.day_of_week, + 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 + AND s.status <> 'cancelled' + ORDER BY s.start_time ASC + `, + [req.user.userId, date], + ); + + res.json({ + date, + routes: result.rows.map((row) => ({ + id: Number(row.id), + startTime: row.start_time, + endTime: row.end_time, + status: row.status, + dayOfWeek: Number(row.day_of_week), + busLine: { + id: Number(row.bus_line_id), + lineNumber: row.line_number, + startStop: row.start_stop, + endStop: row.end_stop, + estimatedDuration: row.estimated_duration_minutes, + description: row.description, + }, + })), + }); + } catch (err) { + console.error("GET /driver/schedules/daily error:", err); + res.status(500).json({ error: "internal_error" }); } }); // ============================================ // ROUTES - Get single route details // ============================================ -app.get('/routes/:routeId', authMiddleware, async (req, res) => { +app.get("/routes/:routeId", authMiddleware, async (req, res) => { try { const { routeId } = req.params; const userId = req.user.userId; - const result = await db.query(` + const result = await db.query( + ` SELECT r.id, r.start_time, @@ -882,10 +1151,12 @@ app.get('/routes/:routeId', authMiddleware, async (req, res) => { JOIN bus_lines bl ON r.bus_line_id = bl.id JOIN bus_types bt ON r.bus_type_id = bt.id WHERE r.id = $1 AND r.user_id = $2 - `, [routeId, userId]); + `, + [routeId, userId], + ); if (result.rows.length === 0) { - return res.status(404).json({ error: 'route_not_found' }); + return res.status(404).json({ error: "route_not_found" }); } const row = result.rows[0]; @@ -913,21 +1184,21 @@ app.get('/routes/:routeId', authMiddleware, async (req, res) => { res.json({ route }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // ROUTES - Get stops for a bus line // ============================================ -app.get('/bus-lines/:busLineId/stops', authMiddleware, async (req, res) => { +app.get("/bus-lines/:busLineId/stops", authMiddleware, async (req, res) => { try { const { busLineId } = req.params; if (DEV_MOCK) { const stops = _mock.routeStops - .filter(rs => String(rs.busLineId) === String(busLineId)) - .sort((a,b) => a.stopOrder - b.stopOrder) - .map(rs => ({ + .filter((rs) => String(rs.busLineId) === String(busLineId)) + .sort((a, b) => a.stopOrder - b.stopOrder) + .map((rs) => ({ id: rs.id, order: rs.stopOrder, name: rs.name, @@ -938,7 +1209,8 @@ app.get('/bus-lines/:busLineId/stops', authMiddleware, async (req, res) => { return res.json({ stops }); } - const result = await db.query(` + const result = await db.query( + ` SELECT rs.id, rs.stop_order, @@ -950,9 +1222,11 @@ app.get('/bus-lines/:busLineId/stops', authMiddleware, async (req, res) => { JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = $1 ORDER BY rs.stop_order ASC - `, [busLineId]); + `, + [busLineId], + ); - const stops = result.rows.map(row => ({ + const stops = result.rows.map((row) => ({ id: row.id, order: row.stop_order, name: row.stop_name, @@ -964,17 +1238,19 @@ app.get('/bus-lines/:busLineId/stops', authMiddleware, async (req, res) => { res.json({ stops }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // ADMIN - Create reusable stop // ============================================ -app.get('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { +app.get("/admin/stops", authMiddleware, adminMiddleware, async (req, res) => { try { if (DEV_MOCK) { - return res.json({ stops: _mock.stops.slice().sort((a, b) => a.id - b.id) }); + return res.json({ + stops: _mock.stops.slice().sort((a, b) => a.id - b.id), + }); } const result = await db.query(` @@ -984,7 +1260,7 @@ app.get('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { `); res.json({ - stops: result.rows.map(row => ({ + stops: result.rows.map((row) => ({ id: row.id, name: row.name, latitude: parseFloat(row.latitude), @@ -995,16 +1271,16 @@ app.get('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); -app.post('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { +app.post("/admin/stops", authMiddleware, adminMiddleware, async (req, res) => { try { const { name, latitude, longitude } = req.body; if (!name || latitude === undefined || longitude === undefined) { - return res.status(400).json({ error: 'missing_required_fields' }); + return res.status(400).json({ error: "missing_required_fields" }); } if (DEV_MOCK) { @@ -1020,7 +1296,8 @@ app.post('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { return res.status(201).json({ stop }); } - const result = await db.query(` + const result = await db.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.stops ) @@ -1028,7 +1305,9 @@ app.post('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { SELECT next_id.id, $1, $2, $3 FROM next_id RETURNING id, name, latitude, longitude, created_at, updated_at - `, [name, latitude, longitude]); + `, + [name, latitude, longitude], + ); const stop = result.rows[0]; res.status(201).json({ @@ -1043,65 +1322,90 @@ app.post('/admin/stops', authMiddleware, adminMiddleware, async (req, res) => { }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // ADMIN - Manage bus lines // ============================================ -app.get('/admin/bus-lines', authMiddleware, adminMiddleware, async (req, res) => { - try { - if (DEV_MOCK) { - return res.json({ busLines: _mock.busLines.slice().sort((a, b) => a.lineNumber - b.lineNumber) }); - } +app.get( + "/admin/bus-lines", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + if (DEV_MOCK) { + return res.json({ + busLines: _mock.busLines + .slice() + .sort((a, b) => a.lineNumber - b.lineNumber), + }); + } - const result = await db.query(` + const result = await db.query(` SELECT id, line_number, start_stop, end_stop, estimated_duration_minutes, description FROM transit.bus_lines ORDER BY line_number ASC, id ASC `); - res.json({ - busLines: result.rows.map(row => ({ - id: row.id, - lineNumber: row.line_number, - startStop: row.start_stop, - endStop: row.end_stop, - estimatedDurationMinutes: row.estimated_duration_minutes, - description: row.description, - })), - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); - -app.post('/admin/bus-lines', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { lineNumber, startStop, endStop, estimatedDurationMinutes, description } = req.body; - - if (lineNumber === undefined || !startStop || !endStop || estimatedDurationMinutes === undefined) { - return res.status(400).json({ error: 'missing_required_fields' }); + res.json({ + busLines: result.rows.map((row) => ({ + id: row.id, + lineNumber: row.line_number, + startStop: row.start_stop, + endStop: row.end_stop, + estimatedDurationMinutes: row.estimated_duration_minutes, + description: row.description, + })), + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); } + }, +); - if (DEV_MOCK) { - const busLine = { - id: _mock.nextBusLineId++, - lineNumber: Number(lineNumber), +app.post( + "/admin/bus-lines", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { + lineNumber, startStop, endStop, - estimatedDurationMinutes: Number(estimatedDurationMinutes), - description: description || '', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - _mock.busLines.push(busLine); - return res.status(201).json({ busLine }); - } + estimatedDurationMinutes, + description, + } = req.body; - const result = await db.query(` + if ( + lineNumber === undefined || + !startStop || + !endStop || + estimatedDurationMinutes === undefined + ) { + return res.status(400).json({ error: "missing_required_fields" }); + } + + if (DEV_MOCK) { + const busLine = { + id: _mock.nextBusLineId++, + lineNumber: Number(lineNumber), + startStop, + endStop, + estimatedDurationMinutes: Number(estimatedDurationMinutes), + description: description || "", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + _mock.busLines.push(busLine); + return res.status(201).json({ busLine }); + } + + const result = await db.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.bus_lines ) @@ -1109,43 +1413,67 @@ app.post('/admin/bus-lines', authMiddleware, adminMiddleware, async (req, res) = SELECT next_id.id, $1, $2, $3, $4, $5 FROM next_id RETURNING id, line_number, start_stop, end_stop, estimated_duration_minutes, description - `, [lineNumber, startStop, endStop, estimatedDurationMinutes, description || null]); + `, + [ + lineNumber, + startStop, + endStop, + estimatedDurationMinutes, + description || null, + ], + ); - const busLine = result.rows[0]; - res.status(201).json({ - busLine: { - id: busLine.id, - lineNumber: busLine.line_number, - startStop: busLine.start_stop, - endStop: busLine.end_stop, - estimatedDurationMinutes: busLine.estimated_duration_minutes, - description: busLine.description, - }, - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); - -app.patch('/admin/bus-lines/:busLineId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { busLineId } = req.params; - const { lineNumber, startStop, endStop, estimatedDurationMinutes, description } = req.body; - - if (DEV_MOCK) { - const existing = _mock.busLines.find(line => String(line.id) === String(busLineId)); - if (!existing) return res.status(404).json({ error: 'bus_line_not_found' }); - if (lineNumber !== undefined) existing.lineNumber = Number(lineNumber); - if (startStop !== undefined) existing.startStop = startStop; - if (endStop !== undefined) existing.endStop = endStop; - if (estimatedDurationMinutes !== undefined) existing.estimatedDurationMinutes = Number(estimatedDurationMinutes); - if (description !== undefined) existing.description = description; - existing.updatedAt = new Date().toISOString(); - return res.json({ busLine: existing }); + const busLine = result.rows[0]; + res.status(201).json({ + busLine: { + id: busLine.id, + lineNumber: busLine.line_number, + startStop: busLine.start_stop, + endStop: busLine.end_stop, + estimatedDurationMinutes: busLine.estimated_duration_minutes, + description: busLine.description, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); } + }, +); - const result = await db.query(` +app.patch( + "/admin/bus-lines/:busLineId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { busLineId } = req.params; + const { + lineNumber, + startStop, + endStop, + estimatedDurationMinutes, + description, + } = req.body; + + if (DEV_MOCK) { + const existing = _mock.busLines.find( + (line) => String(line.id) === String(busLineId), + ); + if (!existing) + return res.status(404).json({ error: "bus_line_not_found" }); + if (lineNumber !== undefined) existing.lineNumber = Number(lineNumber); + if (startStop !== undefined) existing.startStop = startStop; + if (endStop !== undefined) existing.endStop = endStop; + if (estimatedDurationMinutes !== undefined) + existing.estimatedDurationMinutes = Number(estimatedDurationMinutes); + if (description !== undefined) existing.description = description; + existing.updatedAt = new Date().toISOString(); + return res.json({ busLine: existing }); + } + + const result = await db.query( + ` UPDATE transit.bus_lines SET line_number = COALESCE($1, line_number), @@ -1155,100 +1483,135 @@ app.patch('/admin/bus-lines/:busLineId', authMiddleware, adminMiddleware, async description = COALESCE($5, description) WHERE id = $6 RETURNING id, line_number, start_stop, end_stop, estimated_duration_minutes, description - `, [lineNumber ?? null, startStop ?? null, endStop ?? null, estimatedDurationMinutes ?? null, description ?? null, busLineId]); + `, + [ + lineNumber ?? null, + startStop ?? null, + endStop ?? null, + estimatedDurationMinutes ?? null, + description ?? null, + busLineId, + ], + ); - if (!result.rows[0]) return res.status(404).json({ error: 'bus_line_not_found' }); + if (!result.rows[0]) + return res.status(404).json({ error: "bus_line_not_found" }); - const busLine = result.rows[0]; - res.json({ - busLine: { - id: busLine.id, - lineNumber: busLine.line_number, - startStop: busLine.start_stop, - endStop: busLine.end_stop, - estimatedDurationMinutes: busLine.estimated_duration_minutes, - description: busLine.description, - }, - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + const busLine = result.rows[0]; + res.json({ + busLine: { + id: busLine.id, + lineNumber: busLine.line_number, + startStop: busLine.start_stop, + endStop: busLine.end_stop, + estimatedDurationMinutes: busLine.estimated_duration_minutes, + description: busLine.description, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // ADMIN - Manage fleet buses // ============================================ -app.get('/admin/fleet/buses', authMiddleware, adminMiddleware, async (req, res) => { - try { - if (DEV_MOCK) { - const buses = _mock.fleetBuses.slice().sort((a, b) => a.id - b.id); - return res.json({ - buses: buses.map(b => ({ - ...b, - available: !checkBusConflict(b.id, new Date().toISOString(), new Date(Date.now() + 24*60*60*1000).toISOString(), [], _mock.schedules || []) - })) - }); - } +app.get( + "/admin/fleet/buses", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + if (DEV_MOCK) { + const buses = _mock.fleetBuses.slice().sort((a, b) => a.id - b.id); + return res.json({ + buses: buses.map((b) => ({ + ...b, + available: !checkBusConflict( + b.id, + new Date().toISOString(), + new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + [], + _mock.schedules || [], + ), + })), + }); + } - const result = await db.query(` + const result = await db.query(` SELECT id, name, seat_capacity, license_plate FROM fleet.bus ORDER BY id ASC `); - // Get all schedules to check for conflicts - const schedulesResult = await db.query(`SELECT * FROM transit.schedules WHERE status IN ('planned', 'active')`); - const schedules = schedulesResult.rows.map(row => ({ - ...row, - weekdays: parseWeekdaysValue(row.weekdays), - })); + // Get all schedules to check for conflicts + const schedulesResult = await db.query( + `SELECT * FROM transit.schedules WHERE status IN ('planned', 'active')`, + ); + const schedules = schedulesResult.rows.map((row) => ({ + ...row, + weekdays: parseWeekdaysValue(row.weekdays), + })); - res.json({ - buses: result.rows.map(row => { - const busId = row.id; - const now = new Date(); - const tomorrow = new Date(now); - tomorrow.setDate(tomorrow.getDate() + 1); - const isAvailable = !checkBusConflict(busId, now.toISOString(), tomorrow.toISOString(), [], schedules); - - return { - id: row.id, - name: row.name, - seatCapacity: row.seat_capacity, - licensePlate: row.license_plate, - available: isAvailable, + res.json({ + buses: result.rows.map((row) => { + const busId = row.id; + const now = new Date(); + const tomorrow = new Date(now); + tomorrow.setDate(tomorrow.getDate() + 1); + const isAvailable = !checkBusConflict( + busId, + now.toISOString(), + tomorrow.toISOString(), + [], + schedules, + ); + + return { + id: row.id, + name: row.name, + seatCapacity: row.seat_capacity, + licensePlate: row.license_plate, + available: isAvailable, + }; + }), + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); + +app.post( + "/admin/fleet/buses", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { name, seatCapacity, licensePlate } = req.body; + + if (!name || seatCapacity === undefined || !licensePlate) { + return res.status(400).json({ error: "missing_required_fields" }); + } + + if (DEV_MOCK) { + const bus = { + id: _mock.nextFleetBusId++, + name, + seatCapacity: Number(seatCapacity), + licensePlate, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), }; - }), - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + _mock.fleetBuses.push(bus); + return res.status(201).json({ bus }); + } -app.post('/admin/fleet/buses', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { name, seatCapacity, licensePlate } = req.body; - - if (!name || seatCapacity === undefined || !licensePlate) { - return res.status(400).json({ error: 'missing_required_fields' }); - } - - if (DEV_MOCK) { - const bus = { - id: _mock.nextFleetBusId++, - name, - seatCapacity: Number(seatCapacity), - licensePlate, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - _mock.fleetBuses.push(bus); - return res.status(201).json({ bus }); - } - - const result = await db.query(` + const result = await db.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM fleet.bus ) @@ -1256,116 +1619,153 @@ app.post('/admin/fleet/buses', authMiddleware, adminMiddleware, async (req, res) SELECT next_id.id, $1, $2, $3 FROM next_id RETURNING id, name, seat_capacity, license_plate - `, [name, seatCapacity, licensePlate]); + `, + [name, seatCapacity, licensePlate], + ); - const bus = result.rows[0]; - res.status(201).json({ - bus: { - id: bus.id, - name: bus.name, - seatCapacity: bus.seat_capacity, - licensePlate: bus.license_plate, - }, - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + const bus = result.rows[0]; + res.status(201).json({ + bus: { + id: bus.id, + name: bus.name, + seatCapacity: bus.seat_capacity, + licensePlate: bus.license_plate, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // ADMIN - Update fleet bus // ============================================ -app.patch('/admin/fleet/buses/:busId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { busId } = req.params; - const { name, seatCapacity, licensePlate } = req.body; +app.patch( + "/admin/fleet/buses/:busId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { busId } = req.params; + const { name, seatCapacity, licensePlate } = req.body; - if (!busId || !Number.isFinite(Number(busId))) { - return res.status(400).json({ error: 'invalid_bus_id' }); - } + if (!busId || !Number.isFinite(Number(busId))) { + return res.status(400).json({ error: "invalid_bus_id" }); + } - if (DEV_MOCK) { - const bus = _mock.fleetBuses.find(b => Number(b.id) === Number(busId)); - if (!bus) return res.status(404).json({ error: 'bus_not_found' }); - if (name !== undefined) bus.name = name; - if (seatCapacity !== undefined) bus.seatCapacity = Number(seatCapacity); - if (licensePlate !== undefined) bus.licensePlate = licensePlate; - bus.updatedAt = new Date().toISOString(); - return res.json({ bus }); - } + if (DEV_MOCK) { + const bus = _mock.fleetBuses.find( + (b) => Number(b.id) === Number(busId), + ); + if (!bus) return res.status(404).json({ error: "bus_not_found" }); + if (name !== undefined) bus.name = name; + if (seatCapacity !== undefined) bus.seatCapacity = Number(seatCapacity); + if (licensePlate !== undefined) bus.licensePlate = licensePlate; + bus.updatedAt = new Date().toISOString(); + return res.json({ bus }); + } - const result = await db.query(` + const result = await db.query( + ` UPDATE fleet.bus SET name = COALESCE($1, name), seat_capacity = COALESCE($2, seat_capacity), license_plate = COALESCE($3, license_plate), updated_at = NOW() WHERE id = $4 RETURNING id, name, seat_capacity, license_plate - `, [name ?? null, seatCapacity ?? null, licensePlate ?? null, busId]); + `, + [name ?? null, seatCapacity ?? null, licensePlate ?? null, busId], + ); - if (!result.rows[0]) return res.status(404).json({ error: 'bus_not_found' }); + if (!result.rows[0]) + return res.status(404).json({ error: "bus_not_found" }); - const bus = result.rows[0]; - res.json({ bus: { id: bus.id, name: bus.name, seatCapacity: bus.seat_capacity, licensePlate: bus.license_plate } }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + const bus = result.rows[0]; + res.json({ + bus: { + id: bus.id, + name: bus.name, + seatCapacity: bus.seat_capacity, + licensePlate: bus.license_plate, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // ADMIN - Delete fleet bus // ============================================ -app.delete('/admin/fleet/buses/:busId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { busId } = req.params; - if (!busId || !Number.isFinite(Number(busId))) { - return res.status(400).json({ error: 'invalid_bus_id' }); - } +app.delete( + "/admin/fleet/buses/:busId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { busId } = req.params; + if (!busId || !Number.isFinite(Number(busId))) { + return res.status(400).json({ error: "invalid_bus_id" }); + } - if (DEV_MOCK) { - const idx = _mock.fleetBuses.findIndex(b => Number(b.id) === Number(busId)); - if (idx === -1) return res.status(404).json({ error: 'bus_not_found' }); - const deleted = _mock.fleetBuses.splice(idx, 1)[0]; - return res.json({ success: true, bus: deleted }); - } + if (DEV_MOCK) { + const idx = _mock.fleetBuses.findIndex( + (b) => Number(b.id) === Number(busId), + ); + if (idx === -1) return res.status(404).json({ error: "bus_not_found" }); + const deleted = _mock.fleetBuses.splice(idx, 1)[0]; + return res.json({ success: true, bus: deleted }); + } - const result = await db.query(`DELETE FROM fleet.bus WHERE id = $1 RETURNING id, name`, [busId]); - if (result.rows.length === 0) return res.status(404).json({ error: 'bus_not_found' }); - res.json({ success: true, bus: result.rows[0] }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + const result = await db.query( + `DELETE FROM fleet.bus WHERE id = $1 RETURNING id, name`, + [busId], + ); + if (result.rows.length === 0) + return res.status(404).json({ error: "bus_not_found" }); + res.json({ success: true, bus: result.rows[0] }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // ADMIN - Link stop to bus line // ============================================ -app.get('/admin/bus-lines/:busLineId/stops', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { busLineId } = req.params; - if (!busLineId || !Number.isFinite(Number(busLineId))) { - return res.status(400).json({ error: 'invalid_bus_line_id' }); - } +app.get( + "/admin/bus-lines/:busLineId/stops", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { busLineId } = req.params; + if (!busLineId || !Number.isFinite(Number(busLineId))) { + return res.status(400).json({ error: "invalid_bus_line_id" }); + } - if (DEV_MOCK) { - const stops = _mock.routeStops - .filter(rs => String(rs.busLineId) === String(busLineId)) - .sort((a, b) => a.stopOrder - b.stopOrder) - .map(rs => ({ - id: rs.id, - busLineId: rs.busLineId, - stopId: rs.stopId, - stopOrder: rs.stopOrder, - estimatedArrivalMinutes: rs.estimatedArrivalMinutes, - name: rs.name, - latitude: rs.latitude, - longitude: rs.longitude, - })); - return res.json({ routeStops: stops }); - } + if (DEV_MOCK) { + const stops = _mock.routeStops + .filter((rs) => String(rs.busLineId) === String(busLineId)) + .sort((a, b) => a.stopOrder - b.stopOrder) + .map((rs) => ({ + id: rs.id, + busLineId: rs.busLineId, + stopId: rs.stopId, + stopOrder: rs.stopOrder, + estimatedArrivalMinutes: rs.estimatedArrivalMinutes, + name: rs.name, + latitude: rs.latitude, + longitude: rs.longitude, + })); + return res.json({ routeStops: stops }); + } - const result = await db.query(` + const result = await db.query( + ` SELECT rs.id, rs.bus_line_id, @@ -1379,54 +1779,70 @@ app.get('/admin/bus-lines/:busLineId/stops', authMiddleware, adminMiddleware, as JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = $1 ORDER BY rs.stop_order ASC - `, [busLineId]); + `, + [busLineId], + ); - res.json({ routeStops: result.rows.map(row => ({ - id: row.id, - busLineId: row.bus_line_id, - stopId: row.stop_id, - stopOrder: row.stop_order, - estimatedArrivalMinutes: row.estimated_arrival_minutes, - name: row.stop_name, - latitude: parseFloat(row.latitude), - longitude: parseFloat(row.longitude), - })) }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); - -app.post('/admin/bus-lines/:busLineId/stops', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { busLineId } = req.params; - const { stopId, stopOrder, estimatedArrivalMinutes } = req.body; - - if (!stopId || stopOrder === undefined) { - return res.status(400).json({ error: 'missing_required_fields' }); + res.json({ + routeStops: result.rows.map((row) => ({ + id: row.id, + busLineId: row.bus_line_id, + stopId: row.stop_id, + stopOrder: row.stop_order, + estimatedArrivalMinutes: row.estimated_arrival_minutes, + name: row.stop_name, + latitude: parseFloat(row.latitude), + longitude: parseFloat(row.longitude), + })), + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); } + }, +); - if (DEV_MOCK) { - const stop = _mock.stops.find(s => String(s.id) === String(stopId)); - const routeStop = { - id: _mock.nextRouteStopId++, - busLineId: Number(busLineId), - stopId: Number(stopId), - stopOrder: Number(stopOrder), - estimatedArrivalMinutes: estimatedArrivalMinutes ?? null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - name: stop ? stop.name : `stop-${stopId}`, - latitude: stop ? stop.latitude : 0, - longitude: stop ? stop.longitude : 0, - }; - // remove existing with same busLineId & stopOrder - _mock.routeStops = _mock.routeStops.filter(rs => !(rs.busLineId === routeStop.busLineId && rs.stopOrder === routeStop.stopOrder)); - _mock.routeStops.push(routeStop); - return res.status(201).json({ routeStop }); - } +app.post( + "/admin/bus-lines/:busLineId/stops", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { busLineId } = req.params; + const { stopId, stopOrder, estimatedArrivalMinutes } = req.body; - const result = await db.query(` + if (!stopId || stopOrder === undefined) { + return res.status(400).json({ error: "missing_required_fields" }); + } + + if (DEV_MOCK) { + const stop = _mock.stops.find((s) => String(s.id) === String(stopId)); + const routeStop = { + id: _mock.nextRouteStopId++, + busLineId: Number(busLineId), + stopId: Number(stopId), + stopOrder: Number(stopOrder), + estimatedArrivalMinutes: estimatedArrivalMinutes ?? null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + name: stop ? stop.name : `stop-${stopId}`, + latitude: stop ? stop.latitude : 0, + longitude: stop ? stop.longitude : 0, + }; + // remove existing with same busLineId & stopOrder + _mock.routeStops = _mock.routeStops.filter( + (rs) => + !( + rs.busLineId === routeStop.busLineId && + rs.stopOrder === routeStop.stopOrder + ), + ); + _mock.routeStops.push(routeStop); + return res.status(201).json({ routeStop }); + } + + const result = await db.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.route_stops ) @@ -1438,110 +1854,132 @@ app.post('/admin/bus-lines/:busLineId/stops', authMiddleware, adminMiddleware, a estimated_arrival_minutes = EXCLUDED.estimated_arrival_minutes, updated_at = NOW() RETURNING id, bus_line_id, stop_id, stop_order, estimated_arrival_minutes, created_at, updated_at - `, [busLineId, stopId, stopOrder, estimatedArrivalMinutes ?? null]); + `, + [busLineId, stopId, stopOrder, estimatedArrivalMinutes ?? null], + ); - const link = result.rows[0]; - res.status(201).json({ - routeStop: { - id: link.id, - busLineId: link.bus_line_id, - stopId: link.stop_id, - stopOrder: link.stop_order, - estimatedArrivalMinutes: link.estimated_arrival_minutes, - createdAt: link.created_at, - updatedAt: link.updated_at, - }, - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); - -app.post('/admin/bus-lines/:busLineId/stops/reorder', authMiddleware, adminMiddleware, async (req, res) => { - const client = await db.pool.connect(); - try { - const { busLineId } = req.params; - const { routeStopIds } = req.body || {}; - - if (!Array.isArray(routeStopIds) || routeStopIds.length === 0) { - return res.status(400).json({ error: 'routeStopIds array required' }); + const link = result.rows[0]; + res.status(201).json({ + routeStop: { + id: link.id, + busLineId: link.bus_line_id, + stopId: link.stop_id, + stopOrder: link.stop_order, + estimatedArrivalMinutes: link.estimated_arrival_minutes, + createdAt: link.created_at, + updatedAt: link.updated_at, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); } + }, +); - const normalizedIds = routeStopIds.map(id => Number(id)).filter(id => Number.isFinite(id)); - if (normalizedIds.length !== routeStopIds.length) { - return res.status(400).json({ error: 'routeStopIds must contain only numeric ids' }); - } +app.post( + "/admin/bus-lines/:busLineId/stops/reorder", + authMiddleware, + adminMiddleware, + async (req, res) => { + const client = await db.pool.connect(); + try { + const { busLineId } = req.params; + const { routeStopIds } = req.body || {}; - if (DEV_MOCK) { - const busLineStops = _mock.routeStops - .filter(rs => String(rs.busLineId) === String(busLineId)) - .sort((a, b) => a.stopOrder - b.stopOrder); + if (!Array.isArray(routeStopIds) || routeStopIds.length === 0) { + return res.status(400).json({ error: "routeStopIds array required" }); + } + + const normalizedIds = routeStopIds + .map((id) => Number(id)) + .filter((id) => Number.isFinite(id)); + if (normalizedIds.length !== routeStopIds.length) { + return res + .status(400) + .json({ error: "routeStopIds must contain only numeric ids" }); + } + + if (DEV_MOCK) { + const busLineStops = _mock.routeStops + .filter((rs) => String(rs.busLineId) === String(busLineId)) + .sort((a, b) => a.stopOrder - b.stopOrder); + + const idSet = new Set(busLineStops.map((rs) => Number(rs.id))); + const sameCount = busLineStops.length === normalizedIds.length; + const sameIds = sameCount && normalizedIds.every((id) => idSet.has(id)); + if (!sameIds) { + return res + .status(400) + .json({ error: "routeStopIds must match current bus line stops" }); + } + + const updated = normalizedIds.map((routeStopId, index) => { + const stop = _mock.routeStops.find( + (rs) => + Number(rs.id) === routeStopId && + String(rs.busLineId) === String(busLineId), + ); + if (!stop) return null; + stop.stopOrder = index + 1; + stop.updatedAt = new Date().toISOString(); + return stop; + }); + + if (updated.some((item) => !item)) { + return res + .status(400) + .json({ error: "routeStopIds must match current bus line stops" }); + } + + _mock.routeStops = _mock.routeStops.map((routeStop) => { + const reordered = updated.find((item) => item.id === routeStop.id); + return reordered || routeStop; + }); + + return res.json({ + routeStops: updated, + }); + } + + const current = await client.query( + `SELECT id FROM transit.route_stops WHERE bus_line_id = $1 ORDER BY stop_order ASC`, + [busLineId], + ); + + const currentIds = current.rows.map((row) => Number(row.id)); + const sameCount = currentIds.length === normalizedIds.length; + const sameIds = + sameCount && normalizedIds.every((id) => currentIds.includes(id)); - const idSet = new Set(busLineStops.map(rs => Number(rs.id))); - const sameCount = busLineStops.length === normalizedIds.length; - const sameIds = sameCount && normalizedIds.every(id => idSet.has(id)); if (!sameIds) { - return res.status(400).json({ error: 'routeStopIds must match current bus line stops' }); + return res + .status(400) + .json({ error: "routeStopIds must match current bus line stops" }); } - const updated = normalizedIds.map((routeStopId, index) => { - const stop = _mock.routeStops.find(rs => Number(rs.id) === routeStopId && String(rs.busLineId) === String(busLineId)); - if (!stop) return null; - stop.stopOrder = index + 1; - stop.updatedAt = new Date().toISOString(); - return stop; - }); + await client.query("BEGIN"); - if (updated.some(item => !item)) { - return res.status(400).json({ error: 'routeStopIds must match current bus line stops' }); + for (let index = 0; index < normalizedIds.length; index += 1) { + await client.query( + `UPDATE transit.route_stops + SET stop_order = $1, updated_at = NOW() + WHERE id = $2 AND bus_line_id = $3`, + [-(index + 1), normalizedIds[index], busLineId], + ); } - _mock.routeStops = _mock.routeStops.map(routeStop => { - const reordered = updated.find(item => item.id === routeStop.id); - return reordered || routeStop; - }); - - return res.json({ - routeStops: updated, - }); - } - - const current = await client.query( - `SELECT id FROM transit.route_stops WHERE bus_line_id = $1 ORDER BY stop_order ASC`, - [busLineId] - ); - - const currentIds = current.rows.map(row => Number(row.id)); - const sameCount = currentIds.length === normalizedIds.length; - const sameIds = sameCount && normalizedIds.every(id => currentIds.includes(id)); - - if (!sameIds) { - return res.status(400).json({ error: 'routeStopIds must match current bus line stops' }); - } - - await client.query('BEGIN'); - - for (let index = 0; index < normalizedIds.length; index += 1) { - await client.query( - `UPDATE transit.route_stops + for (let index = 0; index < normalizedIds.length; index += 1) { + await client.query( + `UPDATE transit.route_stops SET stop_order = $1, updated_at = NOW() WHERE id = $2 AND bus_line_id = $3`, - [-(index + 1), normalizedIds[index], busLineId] - ); - } + [index + 1, normalizedIds[index], busLineId], + ); + } - for (let index = 0; index < normalizedIds.length; index += 1) { - await client.query( - `UPDATE transit.route_stops - SET stop_order = $1, updated_at = NOW() - WHERE id = $2 AND bus_line_id = $3`, - [index + 1, normalizedIds[index], busLineId] - ); - } - - const result = await client.query( - `SELECT + const result = await client.query( + `SELECT rs.id, rs.stop_order, s.name as stop_name, @@ -1552,238 +1990,274 @@ app.post('/admin/bus-lines/:busLineId/stops/reorder', authMiddleware, adminMiddl JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = $1 ORDER BY rs.stop_order ASC`, - [busLineId] - ); + [busLineId], + ); - await client.query('COMMIT'); + await client.query("COMMIT"); - res.json({ - routeStops: result.rows.map(row => ({ - id: row.id, - order: row.stop_order, - name: row.stop_name, - latitude: parseFloat(row.latitude), - longitude: parseFloat(row.longitude), - estimatedArrivalMinutes: row.estimated_arrival_minutes, - })), - }); - } catch (err) { - await client.query('ROLLBACK').catch(() => {}); - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } finally { - client.release(); - } -}); + res.json({ + routeStops: result.rows.map((row) => ({ + id: row.id, + order: row.stop_order, + name: row.stop_name, + latitude: parseFloat(row.latitude), + longitude: parseFloat(row.longitude), + estimatedArrivalMinutes: row.estimated_arrival_minutes, + })), + }); + } catch (err) { + await client.query("ROLLBACK").catch(() => {}); + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } finally { + client.release(); + } + }, +); // ============================================ // ADMIN - Delete a stop // ============================================ -app.delete('/admin/stops/:stopId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { stopId } = req.params; +app.delete( + "/admin/stops/:stopId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { stopId } = req.params; - if (!stopId || !Number.isFinite(Number(stopId))) { - return res.status(400).json({ error: 'invalid_stop_id' }); - } - - if (DEV_MOCK) { - const index = _mock.stops.findIndex(s => Number(s.id) === Number(stopId)); - if (index === -1) { - return res.status(404).json({ error: 'stop_not_found' }); + if (!stopId || !Number.isFinite(Number(stopId))) { + return res.status(400).json({ error: "invalid_stop_id" }); } - const deletedStop = _mock.stops.splice(index, 1)[0]; - return res.json({ success: true, stop: deletedStop }); - } - const result = await db.query( - `DELETE FROM transit.stops WHERE id = $1 RETURNING id, name`, - [stopId] - ); + if (DEV_MOCK) { + const index = _mock.stops.findIndex( + (s) => Number(s.id) === Number(stopId), + ); + if (index === -1) { + return res.status(404).json({ error: "stop_not_found" }); + } + const deletedStop = _mock.stops.splice(index, 1)[0]; + return res.json({ success: true, stop: deletedStop }); + } - if (result.rows.length === 0) { - return res.status(404).json({ error: 'stop_not_found' }); - } + const result = await db.query( + `DELETE FROM transit.stops WHERE id = $1 RETURNING id, name`, + [stopId], + ); - res.json({ success: true, stop: result.rows[0] }); - } catch (err) { - console.error(err); - if (err.code === '23503') { - return res.status(409).json({ error: 'foreign_key_violation', message: 'This stop is referenced by route_stops' }); + if (result.rows.length === 0) { + return res.status(404).json({ error: "stop_not_found" }); + } + + res.json({ success: true, stop: result.rows[0] }); + } catch (err) { + console.error(err); + if (err.code === "23503") { + return res.status(409).json({ + error: "foreign_key_violation", + message: "This stop is referenced by route_stops", + }); + } + res.status(500).json({ error: "internal_error", details: err.message }); } - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + }, +); // ============================================ // ADMIN - Update a stop // ============================================ -app.patch('/admin/stops/:stopId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { stopId } = req.params; - const { name, latitude, longitude } = req.body; +app.patch( + "/admin/stops/:stopId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { stopId } = req.params; + const { name, latitude, longitude } = req.body; - if (!stopId || !Number.isFinite(Number(stopId))) { - return res.status(400).json({ error: 'invalid_stop_id' }); - } + if (!stopId || !Number.isFinite(Number(stopId))) { + return res.status(400).json({ error: "invalid_stop_id" }); + } - if (DEV_MOCK) { - const stop = _mock.stops.find(s => Number(s.id) === Number(stopId)); - if (!stop) return res.status(404).json({ error: 'stop_not_found' }); - if (name !== undefined) stop.name = name; - if (latitude !== undefined) stop.latitude = parseFloat(latitude); - if (longitude !== undefined) stop.longitude = parseFloat(longitude); - stop.updatedAt = new Date().toISOString(); - return res.json({ stop }); - } + if (DEV_MOCK) { + const stop = _mock.stops.find((s) => Number(s.id) === Number(stopId)); + if (!stop) return res.status(404).json({ error: "stop_not_found" }); + if (name !== undefined) stop.name = name; + if (latitude !== undefined) stop.latitude = parseFloat(latitude); + if (longitude !== undefined) stop.longitude = parseFloat(longitude); + stop.updatedAt = new Date().toISOString(); + return res.json({ stop }); + } - const result = await db.query( - `UPDATE transit.stops + const result = await db.query( + `UPDATE transit.stops SET name = COALESCE($1, name), latitude = COALESCE($2, latitude), longitude = COALESCE($3, longitude), updated_at = NOW() WHERE id = $4 - RETURNING id, name, latitude, longitude, created_at, updated_at` - , [name ?? null, latitude ?? null, longitude ?? null, stopId]); + RETURNING id, name, latitude, longitude, created_at, updated_at`, + [name ?? null, latitude ?? null, longitude ?? null, stopId], + ); - if (!result.rows[0]) return res.status(404).json({ error: 'stop_not_found' }); + if (!result.rows[0]) + return res.status(404).json({ error: "stop_not_found" }); - const stop = result.rows[0]; - res.json({ stop: { - id: stop.id, - name: stop.name, - latitude: parseFloat(stop.latitude), - longitude: parseFloat(stop.longitude), - createdAt: stop.created_at, - updatedAt: stop.updated_at, - }}); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + const stop = result.rows[0]; + res.json({ + stop: { + id: stop.id, + name: stop.name, + latitude: parseFloat(stop.latitude), + longitude: parseFloat(stop.longitude), + createdAt: stop.created_at, + updatedAt: stop.updated_at, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // ADMIN - Delete a bus line // ============================================ -app.delete('/admin/bus-lines/:busLineId', authMiddleware, adminMiddleware, async (req, res) => { - const client = await db.pool.connect(); - try { - const { busLineId } = req.params; +app.delete( + "/admin/bus-lines/:busLineId", + authMiddleware, + adminMiddleware, + async (req, res) => { + const client = await db.pool.connect(); + try { + const { busLineId } = req.params; - if (!busLineId || !Number.isFinite(Number(busLineId))) { - return res.status(400).json({ error: 'invalid_bus_line_id' }); - } - - if (DEV_MOCK) { - const lineIndex = _mock.busLines.findIndex(bl => Number(bl.id) === Number(busLineId)); - if (lineIndex === -1) { - return res.status(404).json({ error: 'bus_line_not_found' }); + if (!busLineId || !Number.isFinite(Number(busLineId))) { + return res.status(400).json({ error: "invalid_bus_line_id" }); } - const deletedLine = _mock.busLines.splice(lineIndex, 1)[0]; - // Also delete route_stops for this line - _mock.routeStops = _mock.routeStops.filter(rs => String(rs.busLineId) !== String(busLineId)); - return res.json({ success: true, busLine: deletedLine }); + + if (DEV_MOCK) { + const lineIndex = _mock.busLines.findIndex( + (bl) => Number(bl.id) === Number(busLineId), + ); + if (lineIndex === -1) { + return res.status(404).json({ error: "bus_line_not_found" }); + } + const deletedLine = _mock.busLines.splice(lineIndex, 1)[0]; + // Also delete route_stops for this line + _mock.routeStops = _mock.routeStops.filter( + (rs) => String(rs.busLineId) !== String(busLineId), + ); + return res.json({ success: true, busLine: deletedLine }); + } + + await client.query("BEGIN"); + + // Delete associated route_stops first + await client.query( + `DELETE FROM transit.route_stops WHERE bus_line_id = $1`, + [busLineId], + ); + + // Delete the bus line + const result = await client.query( + `DELETE FROM transit.bus_lines WHERE id = $1 RETURNING id, line_number`, + [busLineId], + ); + + if (result.rows.length === 0) { + await client.query("ROLLBACK"); + return res.status(404).json({ error: "bus_line_not_found" }); + } + + await client.query("COMMIT"); + res.json({ success: true, busLine: result.rows[0] }); + } catch (err) { + await client.query("ROLLBACK").catch(() => {}); + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } finally { + client.release(); } - - await client.query('BEGIN'); - - // Delete associated route_stops first - await client.query( - `DELETE FROM transit.route_stops WHERE bus_line_id = $1`, - [busLineId] - ); - - // Delete the bus line - const result = await client.query( - `DELETE FROM transit.bus_lines WHERE id = $1 RETURNING id, line_number`, - [busLineId] - ); - - if (result.rows.length === 0) { - await client.query('ROLLBACK'); - return res.status(404).json({ error: 'bus_line_not_found' }); - } - - await client.query('COMMIT'); - res.json({ success: true, busLine: result.rows[0] }); - } catch (err) { - await client.query('ROLLBACK').catch(() => {}); - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } finally { - client.release(); - } -}); + }, +); // ============================================ // ADMIN - Delete a route_stop from bus line // ============================================ -app.delete('/admin/bus-lines/:busLineId/stops/:routeStopId', authMiddleware, adminMiddleware, async (req, res) => { - const client = await db.pool.connect(); - try { - const { busLineId, routeStopId } = req.params; +app.delete( + "/admin/bus-lines/:busLineId/stops/:routeStopId", + authMiddleware, + adminMiddleware, + async (req, res) => { + const client = await db.pool.connect(); + try { + const { busLineId, routeStopId } = req.params; - if (!busLineId || !Number.isFinite(Number(busLineId))) { - return res.status(400).json({ error: 'invalid_bus_line_id' }); - } - - if (!routeStopId || !Number.isFinite(Number(routeStopId))) { - return res.status(400).json({ error: 'invalid_route_stop_id' }); - } - - if (DEV_MOCK) { - const index = _mock.routeStops.findIndex( - rs => Number(rs.id) === Number(routeStopId) && String(rs.busLineId) === String(busLineId) - ); - if (index === -1) { - return res.status(404).json({ error: 'route_stop_not_found' }); + if (!busLineId || !Number.isFinite(Number(busLineId))) { + return res.status(400).json({ error: "invalid_bus_line_id" }); } - const deletedRouteStop = _mock.routeStops.splice(index, 1)[0]; - return res.json({ success: true, routeStop: deletedRouteStop }); - } - await client.query('BEGIN'); + if (!routeStopId || !Number.isFinite(Number(routeStopId))) { + return res.status(400).json({ error: "invalid_route_stop_id" }); + } - // Find and delete the route_stop - const result = await client.query( - `DELETE FROM transit.route_stops + if (DEV_MOCK) { + const index = _mock.routeStops.findIndex( + (rs) => + Number(rs.id) === Number(routeStopId) && + String(rs.busLineId) === String(busLineId), + ); + if (index === -1) { + return res.status(404).json({ error: "route_stop_not_found" }); + } + const deletedRouteStop = _mock.routeStops.splice(index, 1)[0]; + return res.json({ success: true, routeStop: deletedRouteStop }); + } + + await client.query("BEGIN"); + + // Find and delete the route_stop + const result = await client.query( + `DELETE FROM transit.route_stops WHERE id = $1 AND bus_line_id = $2 RETURNING id, stop_order`, - [routeStopId, busLineId] - ); + [routeStopId, busLineId], + ); - if (result.rows.length === 0) { - await client.query('ROLLBACK'); - return res.status(404).json({ error: 'route_stop_not_found' }); - } + if (result.rows.length === 0) { + await client.query("ROLLBACK"); + return res.status(404).json({ error: "route_stop_not_found" }); + } - const deletedOrder = result.rows[0].stop_order; + const deletedOrder = result.rows[0].stop_order; - // Re-order remaining stops - await client.query( - `UPDATE transit.route_stops + // Re-order remaining stops + await client.query( + `UPDATE transit.route_stops SET stop_order = stop_order - 1, updated_at = NOW() WHERE bus_line_id = $1 AND stop_order > $2`, - [busLineId, deletedOrder] - ); + [busLineId, deletedOrder], + ); - await client.query('COMMIT'); - res.json({ success: true, routeStop: result.rows[0] }); - } catch (err) { - await client.query('ROLLBACK').catch(() => {}); - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } finally { - client.release(); - } -}); + await client.query("COMMIT"); + res.json({ success: true, routeStop: result.rows[0] }); + } catch (err) { + await client.query("ROLLBACK").catch(() => {}); + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } finally { + client.release(); + } + }, +); // ============================================ // NAVIGATION - Save calculated route // ============================================ -app.post('/navigation/route', authMiddleware, async (req, res) => { +app.post("/navigation/route", authMiddleware, async (req, res) => { try { const { routeId, @@ -1796,11 +2270,20 @@ app.post('/navigation/route', authMiddleware, async (req, res) => { geojson, } = req.body; - if (!routeId || !startLat || !startLon || !endLat || !endLon || !totalDistance || !totalDuration) { - return res.status(400).json({ error: 'missing_required_fields' }); + if ( + !routeId || + !startLat || + !startLon || + !endLat || + !endLon || + !totalDistance || + !totalDuration + ) { + return res.status(400).json({ error: "missing_required_fields" }); } - const result = await db.query(` + const result = await db.query( + ` INSERT INTO navigation_routes (route_id, start_lat, start_lon, end_lat, end_lon, total_distance_meters, total_duration_seconds, polyline_geojson) @@ -1814,67 +2297,80 @@ app.post('/navigation/route', authMiddleware, async (req, res) => { total_duration_seconds = $7, polyline_geojson = $8 RETURNING * - `, [ - routeId, - startLat, - startLon, - endLat, - endLon, - totalDistance, - totalDuration, - JSON.stringify(geojson), - ]); + `, + [ + routeId, + startLat, + startLon, + endLat, + endLon, + totalDistance, + totalDuration, + JSON.stringify(geojson), + ], + ); res.json({ route: result.rows[0] }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // GPS TRACKING - Record current position // ============================================ -app.post('/gps/track', authMiddleware, async (req, res) => { +app.post("/gps/track", authMiddleware, async (req, res) => { try { const { routeId, latitude, longitude, speed, heading, accuracy } = req.body; if (!routeId || !latitude || !longitude) { - return res.status(400).json({ error: 'missing_required_fields' }); + return res.status(400).json({ error: "missing_required_fields" }); } - await db.query(` + await db.query( + ` INSERT INTO gps_tracking (route_id, latitude, longitude, speed_kmh, heading, accuracy, recorded_at) VALUES ($1, $2, $3, $4, $5, $6, NOW()) - `, [routeId, latitude, longitude, speed || null, heading || null, accuracy || null]); + `, + [ + routeId, + latitude, + longitude, + speed || null, + heading || null, + accuracy || null, + ], + ); res.json({ ok: true }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // GPS TRACKING - Get route history // ============================================ -app.get('/gps/route/:routeId', authMiddleware, async (req, res) => { +app.get("/gps/route/:routeId", authMiddleware, async (req, res) => { try { const { routeId } = req.params; const userId = req.user.userId; // Verify user owns this route const routeCheck = await db.query( - 'SELECT id FROM routes WHERE id = $1 AND user_id = $2', - [routeId, userId] + "SELECT id FROM routes WHERE id = $1 AND user_id = $2", + [routeId, userId], ); if (routeCheck.rows.length === 0) { - return res.status(403).json({ error: 'unauthorized' }); + return res.status(403).json({ error: "unauthorized" }); } - const result = await db.query(` + const result = await db.query( + ` SELECT id, latitude, @@ -1886,9 +2382,11 @@ app.get('/gps/route/:routeId', authMiddleware, async (req, res) => { FROM gps_tracking WHERE route_id = $1 ORDER BY recorded_at ASC - `, [routeId]); + `, + [routeId], + ); - const positions = result.rows.map(row => ({ + const positions = result.rows.map((row) => ({ id: row.id, latitude: parseFloat(row.latitude), longitude: parseFloat(row.longitude), @@ -1901,172 +2399,283 @@ app.get('/gps/route/:routeId', authMiddleware, async (req, res) => { res.json({ positions }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // ROUTES - Update route status // ============================================ -app.patch('/routes/:routeId/status', authMiddleware, async (req, res) => { +app.patch("/routes/:routeId/status", authMiddleware, async (req, res) => { try { const { routeId } = req.params; const { status } = req.body; const userId = req.user.userId; - const validStatuses = ['scheduled', 'in_progress', 'completed', 'cancelled']; + const validStatuses = [ + "scheduled", + "in_progress", + "completed", + "cancelled", + ]; if (!validStatuses.includes(status)) { - return res.status(400).json({ error: 'invalid_status' }); + return res.status(400).json({ error: "invalid_status" }); } - const result = await db.query(` + const result = await db.query( + ` UPDATE transit.routes SET status = $1, updated_at = NOW() WHERE id = $2 AND user_id = $3 RETURNING * - `, [status, routeId, userId]); + `, + [status, routeId, userId], + ); if (result.rows.length === 0) { - return res.status(404).json({ error: 'route_not_found' }); + return res.status(404).json({ error: "route_not_found" }); } res.json({ route: result.rows[0] }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // DRIVER - Update schedule status // ============================================ -app.patch('/driver/schedules/:scheduleId/status', authMiddleware, async (req, res) => { - try { - const { scheduleId } = req.params; - const { status } = req.body; - - const validStatuses = ['planned', 'in_progress', 'completed', 'cancelled', 'active']; - if (!validStatuses.includes(status)) { - return res.status(400).json({ error: 'invalid_status' }); - } - - const normalizedStatus = status === 'active' ? 'in_progress' : status; - - // Fetch the target schedule row to detect recurring templates - const existing = await db.query(`SELECT * FROM transit.schedules WHERE id = $1`, [scheduleId]); - if (existing.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); - } - - const scheduleRow = existing.rows[0]; - const weekdays = parseWeekdaysValue(scheduleRow.weekdays); - - // Allow updating a specific occurrence of a recurring template by passing { date: 'YYYY-MM-DD' } - const requestedDate = req.body.date || null; // optional date key - - if (weekdays && weekdays.length > 0) { - // This is a recurring template. If a date was provided, or the template occurs today, operate on an instance. - let targetDateKey = requestedDate; - if (!targetDateKey) { - const todayKey = new Date().toISOString().slice(0, 10); - if (scheduleOccursOnDate(scheduleRow, todayKey)) { - targetDateKey = todayKey; - } +app.patch( + "/driver/weekly-schedules/:scheduleId/status", + authMiddleware, + async (req, res) => { + try { + const scheduleId = Number(req.params.scheduleId); + const { status } = req.body; + const validStatuses = [ + "planned", + "in_progress", + "completed", + "cancelled", + "active", + ]; + if (!Number.isInteger(scheduleId) || !validStatuses.includes(status)) { + return res.status(400).json({ error: "invalid_status" }); } - if (targetDateKey) { - // Build occurrence start/end based on template times - const templateStart = new Date(scheduleRow.start_time ?? scheduleRow.startTime); - const templateEnd = new Date(scheduleRow.end_time ?? scheduleRow.endTime); - const occurrenceStart = new Date(`${targetDateKey}T00:00:00`); - occurrenceStart.setHours( - templateStart.getHours(), - templateStart.getMinutes(), - templateStart.getSeconds(), - templateStart.getMilliseconds() - ); + const normalizedStatus = status === "active" ? "in_progress" : status; + const result = await db.query( + ` + UPDATE transit.schedules s + SET status = $1, updated_at = NOW() + WHERE s.id = $2 + AND EXISTS ( + SELECT 1 FROM transit.shifts sh + WHERE sh.schedule_id = s.id AND sh.driver_id = $3 + ) + RETURNING s.id, s.status + `, + [normalizedStatus, scheduleId, req.user.userId], + ); - let occurrenceEnd = null; - if (Number.isFinite(templateEnd.getTime()) && Number.isFinite(templateStart.getTime())) { - const duration = templateEnd.getTime() - templateStart.getTime(); - occurrenceEnd = new Date(occurrenceStart.getTime() + duration); + if (result.rows.length === 0) + return res.status(404).json({ error: "schedule_not_found" }); + res.json({ + schedule: { + id: Number(result.rows[0].id), + status: result.rows[0].status, + }, + }); + } catch (err) { + console.error("PATCH /driver/weekly-schedules/:id/status error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); + +app.patch( + "/driver/schedules/:scheduleId/status", + authMiddleware, + async (req, res) => { + try { + const { scheduleId } = req.params; + const { status } = req.body; + + const validStatuses = [ + "planned", + "in_progress", + "completed", + "cancelled", + "active", + ]; + if (!validStatuses.includes(status)) { + return res.status(400).json({ error: "invalid_status" }); + } + + const normalizedStatus = status === "active" ? "in_progress" : status; + + // Fetch the target schedule row to detect recurring templates + const existing = await db.query( + `SELECT * FROM transit.schedules WHERE id = $1`, + [scheduleId], + ); + if (existing.rows.length === 0) { + return res.status(404).json({ error: "schedule_not_found" }); + } + + const scheduleRow = existing.rows[0]; + const weekdays = parseWeekdaysValue(scheduleRow.weekdays); + + // Allow updating a specific occurrence of a recurring template by passing { date: 'YYYY-MM-DD' } + const requestedDate = req.body.date || null; // optional date key + + if (weekdays && weekdays.length > 0) { + // This is a recurring template. If a date was provided, or the template occurs today, operate on an instance. + let targetDateKey = requestedDate; + if (!targetDateKey) { + const todayKey = new Date().toISOString().slice(0, 10); + if (scheduleOccursOnDate(scheduleRow, todayKey)) { + targetDateKey = todayKey; + } } - // Check if a concrete instance already exists for this template/date - const existingInstance = await db.query( - `SELECT * FROM transit.schedules WHERE template_id = $1 AND DATE(start_time) = $2`, - [scheduleId, targetDateKey] - ); + if (targetDateKey) { + // Build occurrence start/end based on template times + const templateStart = new Date( + scheduleRow.start_time ?? scheduleRow.startTime, + ); + const templateEnd = new Date( + scheduleRow.end_time ?? scheduleRow.endTime, + ); + const occurrenceStart = new Date(`${targetDateKey}T00:00:00`); + occurrenceStart.setHours( + templateStart.getHours(), + templateStart.getMinutes(), + templateStart.getSeconds(), + templateStart.getMilliseconds(), + ); - if (existingInstance.rows.length > 0) { - const upd = await db.query(` + let occurrenceEnd = null; + if ( + Number.isFinite(templateEnd.getTime()) && + Number.isFinite(templateStart.getTime()) + ) { + const duration = templateEnd.getTime() - templateStart.getTime(); + occurrenceEnd = new Date(occurrenceStart.getTime() + duration); + } + + // Check if a concrete instance already exists for this template/date + const existingInstance = await db.query( + `SELECT * FROM transit.schedules WHERE template_id = $1 AND DATE(start_time) = $2`, + [scheduleId, targetDateKey], + ); + + if (existingInstance.rows.length > 0) { + const upd = await db.query( + ` UPDATE transit.schedules SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING * - `, [normalizedStatus, existingInstance.rows[0].id]); - return res.json({ schedule: upd.rows[0] }); - } + `, + [normalizedStatus, existingInstance.rows[0].id], + ); + return res.json({ schedule: upd.rows[0] }); + } - // Insert a new instance derived from template - const hasTemplateIdCol = await db.query(` + // Insert a new instance derived from template + const hasTemplateIdCol = await db.query(` SELECT 1 FROM information_schema.columns WHERE table_schema='transit' AND table_name='schedules' AND column_name='template_id' `); - let insertResult; - if (hasTemplateIdCol.rows.length > 0) { - insertResult = await db.query(` + let insertResult; + if (hasTemplateIdCol.rows.length > 0) { + insertResult = await db.query( + ` WITH next_id AS (SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules) INSERT INTO transit.schedules (id, bus_line_id, bus_id, driver_id, start_time, end_time, status, template_id) SELECT next_id.id, $1, $2, $3, $4, $5, $6, $7 FROM next_id RETURNING * - `, [scheduleRow.bus_line_id, scheduleRow.bus_id, scheduleRow.driver_id, occurrenceStart.toISOString(), occurrenceEnd ? occurrenceEnd.toISOString() : null, normalizedStatus, scheduleId]); - } else { - insertResult = await db.query(` + `, + [ + scheduleRow.bus_line_id, + scheduleRow.bus_id, + scheduleRow.driver_id, + occurrenceStart.toISOString(), + occurrenceEnd ? occurrenceEnd.toISOString() : null, + normalizedStatus, + scheduleId, + ], + ); + } else { + insertResult = await db.query( + ` WITH next_id AS (SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules) INSERT INTO transit.schedules (id, bus_line_id, bus_id, driver_id, start_time, end_time, status) SELECT next_id.id, $1, $2, $3, $4, $5, $6 FROM next_id RETURNING * - `, [scheduleRow.bus_line_id, scheduleRow.bus_id, scheduleRow.driver_id, occurrenceStart.toISOString(), occurrenceEnd ? occurrenceEnd.toISOString() : null, normalizedStatus]); + `, + [ + scheduleRow.bus_line_id, + scheduleRow.bus_id, + scheduleRow.driver_id, + occurrenceStart.toISOString(), + occurrenceEnd ? occurrenceEnd.toISOString() : null, + normalizedStatus, + ], + ); + } + + return res.json({ schedule: insertResult.rows[0] }); } - - return res.json({ schedule: insertResult.rows[0] }); } - } - // Fallback: update the schedule row directly (one-time or when no occurrence date is inferred) - const result = await db.query(` + // Fallback: update the schedule row directly (one-time or when no occurrence date is inferred) + const result = await db.query( + ` UPDATE transit.schedules SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING * - `, [normalizedStatus, scheduleId]); + `, + [normalizedStatus, scheduleId], + ); - if (result.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); + if (result.rows.length === 0) { + return res.status(404).json({ error: "schedule_not_found" }); + } + + res.json({ schedule: result.rows[0] }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); } - - res.json({ schedule: result.rows[0] }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + }, +); // ============================================ // ADMIN - Get drivers for scheduling // ============================================ -app.get('/admin/drivers', authMiddleware, adminMiddleware, async (req, res) => { +app.get("/admin/drivers", authMiddleware, adminMiddleware, async (req, res) => { try { if (DEV_MOCK) { - const drivers = _mock.drivers.slice().filter(d => d.status === 'active'); - return res.json({ - drivers: drivers.map(d => ({ + const drivers = _mock.drivers + .slice() + .filter((d) => d.status === "active"); + return res.json({ + drivers: drivers.map((d) => ({ ...d, - available: !checkDriverConflict(d.id, new Date().toISOString(), new Date(Date.now() + 24*60*60*1000).toISOString(), [], _mock.schedules || []) - })) + available: !checkDriverConflict( + d.id, + new Date().toISOString(), + new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + [], + _mock.schedules || [], + ), + })), }); } @@ -2078,23 +2687,35 @@ app.get('/admin/drivers', authMiddleware, adminMiddleware, async (req, res) => { `); // Get all schedules to check for conflicts - const schedulesResult = await db.query(`SELECT * FROM transit.schedules WHERE status IN ('planned', 'active')`); - const schedules = schedulesResult.rows.map(row => ({ + const schedulesResult = await db.query( + `SELECT * FROM transit.schedules WHERE status IN ('planned', 'active')`, + ); + const schedules = schedulesResult.rows.map((row) => ({ ...row, weekdays: parseWeekdaysValue(row.weekdays), })); res.json({ - drivers: result.rows.map(row => { + drivers: result.rows.map((row) => { const driverId = row.id; const now = new Date(); const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); - const isAvailable = !checkDriverConflict(driverId, now.toISOString(), tomorrow.toISOString(), [], schedules); - + const isAvailable = !checkDriverConflict( + driverId, + now.toISOString(), + tomorrow.toISOString(), + [], + schedules, + ); + return { id: row.id, - name: (String(row.firstname || '') + ' ' + String(row.lastname || '')).trim(), + name: ( + String(row.firstname || "") + + " " + + String(row.lastname || "") + ).trim(), email: row.email, phone: row.phone, job: row.job, @@ -2106,65 +2727,389 @@ app.get('/admin/drivers', authMiddleware, adminMiddleware, async (req, res) => { }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // ADMIN - Manage schedules (timetable) // ============================================ -app.get('/admin/schedules', authMiddleware, adminMiddleware, async (req, res) => { - try { - const templatesOnly = String(req.query.templates || req.query.includeTemplates || '').toLowerCase() === 'true' || String(req.query.templates || req.query.includeTemplates || '') === '1'; +app.get( + "/admin/shift-drivers", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const result = await db.query(` + SELECT id, usercode + FROM workers.users + ORDER BY usercode ASC + `); + res.json({ + drivers: result.rows.map((row) => ({ + id: Number(row.id), + name: String(row.usercode), + })), + }); + } catch (err) { + console.error("GET /admin/shift-drivers error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); - if (DEV_MOCK) { - const routeStopsByLine = new Map(); - for (const routeStop of _mock.routeStops || []) { - const busLineId = Number(routeStop.busLineId); - if (!routeStopsByLine.has(busLineId)) { - routeStopsByLine.set(busLineId, []); - } - routeStopsByLine.get(busLineId).push({ - stop_id: routeStop.stopId, - stop_order: routeStop.stopOrder, +app.get( + "/admin/weekly-schedules", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const result = await db.query(` + SELECT s.id, s.bus_line_id, s.start_time, s.end_time, s.status, s.day_of_week, + bl.line_number, bl.start_stop, bl.end_stop, + sh.id AS shift_id, sh.driver_id, u.usercode AS driver_name + FROM transit.schedules s + JOIN transit.bus_lines bl ON bl.id = s.bus_line_id + LEFT JOIN transit.shifts sh ON sh.schedule_id = s.id + LEFT JOIN workers.users u ON u.id = sh.driver_id + ORDER BY s.day_of_week ASC, s.start_time ASC, s.id ASC + `); + res.json({ + schedules: result.rows.map((row) => ({ + id: Number(row.id), + busLineId: Number(row.bus_line_id), + lineNumber: row.line_number, + startStop: row.start_stop, + endStop: row.end_stop, + startTime: row.start_time, + endTime: row.end_time, + status: row.status, + dayOfWeek: Number(row.day_of_week), + shiftId: row.shift_id ? Number(row.shift_id) : null, + driverId: row.driver_id ? Number(row.driver_id) : null, + driverName: row.driver_name || null, + })), + }); + } catch (err) { + console.error("GET /admin/weekly-schedules error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); + +app.post( + "/admin/weekly-schedules", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const busLineId = Number(req.body?.busLineId); + const dayOfWeek = Number(req.body?.dayOfWeek); + const startTime = String(req.body?.startTime || ""); + const endTime = String(req.body?.endTime || ""); + const driverId = req.body?.driverId ? Number(req.body.driverId) : null; + + if ( + !Number.isInteger(busLineId) || + !Number.isInteger(dayOfWeek) || + dayOfWeek < 0 || + dayOfWeek > 6 || + !startTime || + !endTime + ) { + return res.status(400).json({ + error: "busLineId, dayOfWeek, startTime and endTime are required", }); } - const rawSchedules = (_mock.schedules || []).map(s => { - const busLine = _mock.busLines.find(bl => bl.id === s.busLineId); - const bus = _mock.fleetBuses.find(b => b.id === s.busId); - return { - ...s, - busLineId: s.busLineId, - busId: s.busId, - startTime: s.startTime, - endTime: s.endTime, - weekdays: s.weekdays || [], - status: s.status, - lineNumber: busLine?.lineNumber || 0, - busName: bus?.name || 'Unknown Bus', - driverId: s.driverId || null, - estimatedDurationMinutes: busLine?.estimatedDurationMinutes || 60, - }; - }); + const created = await db.query( + ` + INSERT INTO transit.schedules (bus_line_id, start_time, end_time, status, day_of_week) + VALUES ($1, $2::time, $3::time, 'planned', $4) + RETURNING id, bus_line_id, start_time, end_time, status, day_of_week + `, + [busLineId, startTime, endTime, dayOfWeek], + ); - const schedules = (templatesOnly ? rawSchedules : expandRecurringScheduleRows(rawSchedules)) - .filter(row => scheduleMatchesFilters(row, parseScheduleFilters(req.query), routeStopsByLine)) - .map(s => ({ - ...s, - departureTimes: calculateDepartureTimes(s.startTime, s.busLineId, _mock), - })) - .sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); - - return res.json({ schedules }); + const schedule = created.rows[0]; + if (driverId) { + await db.query( + ` + INSERT INTO transit.shifts (schedule_id, driver_id) + VALUES ($1, $2) + `, + [schedule.id, driverId], + ); + } + + res.status(201).json({ + schedule: { + id: Number(schedule.id), + busLineId: Number(schedule.bus_line_id), + startTime: schedule.start_time, + endTime: schedule.end_time, + status: schedule.status, + dayOfWeek: Number(schedule.day_of_week), + driverId, + }, + }); + } catch (err) { + console.error("POST /admin/weekly-schedules error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); + +app.post( + "/admin/weekly-schedules/bulk", + authMiddleware, + adminMiddleware, + async (req, res) => { + const busLineId = Number(req.body?.busLineId); + const dayOfWeeks = Array.isArray(req.body?.dayOfWeeks) + ? req.body.dayOfWeeks + .map(Number) + .filter((day) => Number.isInteger(day) && day >= 0 && day <= 6) + : []; + const startTimes = Array.isArray(req.body?.startTimes) + ? [...new Set(req.body.startTimes.map(String).filter(Boolean))] + : []; + const driverId = req.body?.driverId ? Number(req.body.driverId) : null; + + if ( + !Number.isInteger(busLineId) || + dayOfWeeks.length === 0 || + startTimes.length === 0 + ) { + return res + .status(400) + .json({ error: "busLineId, dayOfWeeks and startTimes are required" }); } - const hasWeekdaysColumn = await db.query(` + const client = await db.pool.connect(); + try { + await client.query("BEGIN"); + const line = await client.query( + "SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1", + [busLineId], + ); + if (line.rows.length === 0) { + await client.query("ROLLBACK"); + return res.status(404).json({ error: "bus_line_not_found" }); + } + + const duration = Number(line.rows[0].estimated_duration_minutes) || 60; + const created = []; + for (const dayOfWeek of dayOfWeeks) { + for (const startTime of startTimes) { + const result = await client.query( + `INSERT INTO transit.schedules + (bus_line_id, start_time, end_time, status, day_of_week) + VALUES ($1, $2::time, ($2::time + ($3::text || ' minutes')::interval)::time, 'planned', $4) + RETURNING id, bus_line_id, start_time, end_time, status, day_of_week`, + [busLineId, startTime, duration, dayOfWeek], + ); + const schedule = result.rows[0]; + if (driverId) { + await client.query( + "INSERT INTO transit.shifts (schedule_id, driver_id) VALUES ($1, $2)", + [schedule.id, driverId], + ); + } + created.push(schedule); + } + } + + await client.query("COMMIT"); + res.status(201).json({ + createdCount: created.length, + schedules: created.map((schedule) => ({ + id: Number(schedule.id), + busLineId: Number(schedule.bus_line_id), + startTime: schedule.start_time, + endTime: schedule.end_time, + status: schedule.status, + dayOfWeek: Number(schedule.day_of_week), + driverId, + })), + }); + } catch (err) { + await client.query("ROLLBACK"); + console.error("POST /admin/weekly-schedules/bulk error:", err); + res.status(500).json({ error: "internal_error" }); + } finally { + client.release(); + } + }, +); + +app.put( + "/admin/weekly-schedules/:scheduleId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const scheduleId = Number(req.params.scheduleId); + const busLineId = Number(req.body?.busLineId); + const dayOfWeek = Number(req.body?.dayOfWeek); + const startTime = String(req.body?.startTime || ""); + const endTime = String(req.body?.endTime || ""); + if ( + !Number.isInteger(scheduleId) || + !Number.isInteger(busLineId) || + !Number.isInteger(dayOfWeek) || + dayOfWeek < 0 || + dayOfWeek > 6 || + !startTime || + !endTime + ) { + return res.status(400).json({ error: "invalid_schedule" }); + } + + const result = await db.query( + ` + UPDATE transit.schedules + SET bus_line_id = $2, start_time = $3::time, end_time = $4::time, + day_of_week = $5, updated_at = NOW() + WHERE id = $1 + RETURNING id, bus_line_id, start_time, end_time, status, day_of_week + `, + [scheduleId, busLineId, startTime, endTime, dayOfWeek], + ); + if (result.rows.length === 0) + return res.status(404).json({ error: "schedule_not_found" }); + res.json({ schedule: result.rows[0] }); + } catch (err) { + console.error("PUT /admin/weekly-schedules error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); + +app.put( + "/admin/weekly-schedules/:scheduleId/shift", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const scheduleId = Number(req.params.scheduleId); + const driverId = req.body?.driverId ? Number(req.body.driverId) : null; + if (!Number.isInteger(scheduleId)) + return res.status(400).json({ error: "invalid_schedule_id" }); + await db.query("DELETE FROM transit.shifts WHERE schedule_id = $1", [ + scheduleId, + ]); + if (driverId) { + await db.query( + "INSERT INTO transit.shifts (schedule_id, driver_id) VALUES ($1, $2)", + [scheduleId, driverId], + ); + } + res.json({ scheduleId, driverId }); + } catch (err) { + console.error("PUT /admin/weekly-schedules/:id/shift error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); + +app.delete( + "/admin/weekly-schedules/:scheduleId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const scheduleId = Number(req.params.scheduleId); + if (!Number.isInteger(scheduleId)) + return res.status(400).json({ error: "invalid_schedule_id" }); + const result = await db.query( + "DELETE FROM transit.schedules WHERE id = $1 RETURNING id", + [scheduleId], + ); + if (result.rows.length === 0) + return res.status(404).json({ error: "schedule_not_found" }); + res.json({ success: true, scheduleId }); + } catch (err) { + console.error("DELETE /admin/weekly-schedules error:", err); + res.status(500).json({ error: "internal_error" }); + } + }, +); + +app.get( + "/admin/schedules", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const templatesOnly = + String( + req.query.templates || req.query.includeTemplates || "", + ).toLowerCase() === "true" || + String(req.query.templates || req.query.includeTemplates || "") === "1"; + + if (DEV_MOCK) { + const routeStopsByLine = new Map(); + for (const routeStop of _mock.routeStops || []) { + const busLineId = Number(routeStop.busLineId); + if (!routeStopsByLine.has(busLineId)) { + routeStopsByLine.set(busLineId, []); + } + routeStopsByLine.get(busLineId).push({ + stop_id: routeStop.stopId, + stop_order: routeStop.stopOrder, + }); + } + + const rawSchedules = (_mock.schedules || []).map((s) => { + const busLine = _mock.busLines.find((bl) => bl.id === s.busLineId); + const bus = _mock.fleetBuses.find((b) => b.id === s.busId); + return { + ...s, + busLineId: s.busLineId, + busId: s.busId, + startTime: s.startTime, + endTime: s.endTime, + weekdays: s.weekdays || [], + status: s.status, + lineNumber: busLine?.lineNumber || 0, + busName: bus?.name || "Unknown Bus", + driverId: s.driverId || null, + estimatedDurationMinutes: busLine?.estimatedDurationMinutes || 60, + }; + }); + + const schedules = ( + templatesOnly + ? rawSchedules + : expandRecurringScheduleRows(rawSchedules) + ) + .filter((row) => + scheduleMatchesFilters( + row, + parseScheduleFilters(req.query), + routeStopsByLine, + ), + ) + .map((s) => ({ + ...s, + departureTimes: calculateDepartureTimes( + s.startTime, + s.busLineId, + _mock, + ), + })) + .sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); + + return res.json({ schedules }); + } + + const hasWeekdaysColumn = await db.query(` SELECT 1 FROM information_schema.columns WHERE table_schema='transit' AND table_name='schedules' AND column_name='weekdays' `); - const baseSelect = ` + const baseSelect = ` SELECT s.id, s.bus_line_id, @@ -2180,31 +3125,33 @@ app.get('/admin/schedules', authMiddleware, adminMiddleware, async (req, res) => b.license_plate, TRIM(COALESCE(e.firstname, '') || ' ' || COALESCE(e.lastname, '')) as driver_name`; - const result = hasWeekdaysColumn.rows.length > 0 - ? await db.query(`${baseSelect}, s.weekdays as weekdays + const result = + hasWeekdaysColumn.rows.length > 0 + ? await db.query(`${baseSelect}, s.weekdays as weekdays FROM transit.schedules s LEFT JOIN transit.bus_lines bl ON s.bus_line_id = bl.id LEFT JOIN fleet.bus b ON s.bus_id = b.id LEFT JOIN workers.employees e ON s.driver_id = e.id ORDER BY s.start_time DESC`) - : await db.query(`${baseSelect}, NULL as weekdays + : await db.query(`${baseSelect}, NULL as weekdays FROM transit.schedules s LEFT JOIN transit.bus_lines bl ON s.bus_line_id = bl.id LEFT JOIN fleet.bus b ON s.bus_id = b.id LEFT JOIN workers.employees e ON s.driver_id = e.id ORDER BY s.start_time DESC`); - const lineIds = Array.from( - new Set( - result.rows - .map(row => Number(row.bus_line_id)) - .filter(Number.isFinite) - ) - ); + const lineIds = Array.from( + new Set( + result.rows + .map((row) => Number(row.bus_line_id)) + .filter(Number.isFinite), + ), + ); - const routeStopsByLine = new Map(); - if (lineIds.length > 0) { - const routeStopsResult = await db.query(` + const routeStopsByLine = new Map(); + if (lineIds.length > 0) { + const routeStopsResult = await db.query( + ` SELECT rs.bus_line_id, rs.stop_id, @@ -2215,98 +3162,125 @@ app.get('/admin/schedules', authMiddleware, adminMiddleware, async (req, res) => JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = ANY($1::int[]) ORDER BY rs.bus_line_id ASC, rs.stop_order ASC - `, [lineIds]); + `, + [lineIds], + ); - for (const row of routeStopsResult.rows) { - const busLineId = Number(row.bus_line_id); - if (!routeStopsByLine.has(busLineId)) { - routeStopsByLine.set(busLineId, []); + for (const row of routeStopsResult.rows) { + const busLineId = Number(row.bus_line_id); + if (!routeStopsByLine.has(busLineId)) { + routeStopsByLine.set(busLineId, []); + } + routeStopsByLine.get(busLineId).push(row); } - routeStopsByLine.get(busLineId).push(row); } + + const filters = parseScheduleFilters(req.query); + const rowSource = templatesOnly + ? result.rows + : expandRecurringScheduleRows(result.rows); + const expandedSchedules = rowSource.filter((row) => + scheduleMatchesFilters(row, filters, routeStopsByLine), + ); + + const schedules = expandedSchedules + .map((row) => mapAdminScheduleRow(row, routeStopsByLine)) + .sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); + + res.json({ schedules }); + } catch (err) { + console.error("GET /admin/schedules error:", err); + res.status(500).json({ error: "internal_error", details: err.message }); } + }, +); - const filters = parseScheduleFilters(req.query); - const rowSource = templatesOnly ? result.rows : expandRecurringScheduleRows(result.rows) - const expandedSchedules = rowSource - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)); +app.post( + "/admin/schedules", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { busLineId, busId, driverId, startTime, weekdays } = req.body; - const schedules = expandedSchedules - .map(row => mapAdminScheduleRow(row, routeStopsByLine)) - .sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); + if (!busLineId || !busId || !startTime) { + return res.status(400).json({ + error: + "missing_required_fields: busLineId, busId, startTime required", + }); + } - res.json({ schedules }); - } catch (err) { - console.error('GET /admin/schedules error:', err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + // Validate weekdays - should be array of day numbers (0-6) or empty for single schedule + const scheduleWeekdays = + Array.isArray(weekdays) && weekdays.length > 0 + ? weekdays.map((w) => Number(w)).filter((w) => w >= 0 && w <= 6) + : []; -app.post('/admin/schedules', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { busLineId, busId, driverId, startTime, weekdays } = req.body; + if (DEV_MOCK) { + if (!_mock.schedules) _mock.schedules = []; - if (!busLineId || !busId || !startTime) { - return res.status(400).json({ error: 'missing_required_fields: busLineId, busId, startTime required' }); - } + // Find the bus line to get duration + const busLine = _mock.busLines.find((bl) => bl.id == busLineId); + if (!busLine) { + return res.status(404).json({ error: "bus_line_not_found" }); + } - // Validate weekdays - should be array of day numbers (0-6) or empty for single schedule - const scheduleWeekdays = Array.isArray(weekdays) && weekdays.length > 0 - ? weekdays.map(w => Number(w)).filter(w => w >= 0 && w <= 6) - : []; + const startDate = new Date(startTime); + const endDate = new Date(startDate); + endDate.setMinutes( + endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60), + ); - if (DEV_MOCK) { - if (!_mock.schedules) _mock.schedules = []; - - // Find the bus line to get duration - const busLine = _mock.busLines.find(bl => bl.id == busLineId); - if (!busLine) { - return res.status(404).json({ error: 'bus_line_not_found' }); + // Calculate departure times for stops + const departureTimes = calculateDepartureTimes( + startTime, + Number(busLineId), + _mock, + ); + + const schedule = { + id: _mock.nextScheduleId || 1, + busLineId, + busId, + driverId: driverId ? Number(driverId) : null, + startTime, + endTime: endDate.toISOString(), + weekdays: scheduleWeekdays, + departureTimes, + status: "planned", + createdAt: new Date().toISOString(), + }; + _mock.nextScheduleId = (_mock.nextScheduleId || 1) + 1; + _mock.schedules.push(schedule); + return res.status(201).json({ schedule }); + } + + // Database mode (production) + const busLine = await db.query( + "SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1", + [busLineId], + ); + if (!busLine.rows[0]) { + return res.status(404).json({ error: "bus_line_not_found" }); } const startDate = new Date(startTime); const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60)); + endDate.setMinutes( + endDate.getMinutes() + + (busLine.rows[0].estimated_duration_minutes || 60), + ); - // Calculate departure times for stops - const departureTimes = calculateDepartureTimes(startTime, Number(busLineId), _mock); - - const schedule = { - id: (_mock.nextScheduleId || 1), - busLineId, - busId, - driverId: driverId ? Number(driverId) : null, - startTime, - endTime: endDate.toISOString(), - weekdays: scheduleWeekdays, - departureTimes, - status: 'planned', - createdAt: new Date().toISOString(), - }; - _mock.nextScheduleId = ((_mock.nextScheduleId || 1) + 1); - _mock.schedules.push(schedule); - return res.status(201).json({ schedule }); - } - - // Database mode (production) - const busLine = await db.query('SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1', [busLineId]); - if (!busLine.rows[0]) { - return res.status(404).json({ error: 'bus_line_not_found' }); - } - - const startDate = new Date(startTime); - const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + (busLine.rows[0].estimated_duration_minutes || 60)); - - // Check if weekdays column exists before including it in INSERT - const hasWeekdaysColumn = await db.query(` + // Check if weekdays column exists before including it in INSERT + const hasWeekdaysColumn = await db.query(` SELECT 1 FROM information_schema.columns WHERE table_schema='transit' AND table_name='schedules' AND column_name='weekdays' `); - - let result; - if (hasWeekdaysColumn.rows.length > 0) { - result = await db.query(` + + let result; + if (hasWeekdaysColumn.rows.length > 0) { + result = await db.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules ) @@ -2314,9 +3288,19 @@ app.post('/admin/schedules', authMiddleware, adminMiddleware, async (req, res) = SELECT next_id.id, $1, $2, $3, $4, $5, 'planned', $6 FROM next_id RETURNING id, bus_line_id, bus_id, driver_id, start_time, end_time, status, weekdays, created_at - `, [busLineId, busId, driverId || null, startTime, endDate.toISOString(), JSON.stringify(scheduleWeekdays)]); - } else { - result = await db.query(` + `, + [ + busLineId, + busId, + driverId || null, + startTime, + endDate.toISOString(), + JSON.stringify(scheduleWeekdays), + ], + ); + } else { + result = await db.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules ) @@ -2324,106 +3308,144 @@ app.post('/admin/schedules', authMiddleware, adminMiddleware, async (req, res) = SELECT next_id.id, $1, $2, $3, $4, $5, 'planned' FROM next_id RETURNING id, bus_line_id, bus_id, driver_id, start_time, end_time, status, created_at - `, [busLineId, busId, driverId || null, startTime, endDate.toISOString()]); - } - - const schedule = result.rows[0]; - const departureTimes = calculateDepartureTimes(startTime, busLineId, _mock); - - const weekdaysResult = parseWeekdaysValue(schedule.weekdays); - - res.status(201).json({ - schedule: { - id: Number(schedule.id), - busLineId: Number(schedule.bus_line_id), - busId: Number(schedule.bus_id), - driverId: schedule.driver_id !== null && schedule.driver_id !== undefined ? Number(schedule.driver_id) : null, - startTime: schedule.start_time, - endTime: schedule.end_time, - weekdays: weekdaysResult, - departureTimes, - status: schedule.status, - createdAt: schedule.created_at, - }, - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); - -app.put('/admin/schedules/:scheduleId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { scheduleId } = req.params; - const { busLineId, busId, driverId, startTime, weekdays } = req.body; - - if (!scheduleId || !Number.isFinite(Number(scheduleId))) { - return res.status(400).json({ error: 'invalid_schedule_id' }); - } - - if (!busLineId || !busId || !startTime) { - return res.status(400).json({ error: 'missing_required_fields: busLineId, busId, startTime required' }); - } - - const scheduleWeekdays = Array.isArray(weekdays) && weekdays.length > 0 - ? weekdays.map(w => Number(w)).filter(w => w >= 0 && w <= 6) - : []; - - if (DEV_MOCK) { - const index = (_mock.schedules || []).findIndex(s => Number(s.id) === Number(scheduleId)); - if (index === -1) { - return res.status(404).json({ error: 'schedule_not_found' }); + `, + [ + busLineId, + busId, + driverId || null, + startTime, + endDate.toISOString(), + ], + ); } - const busLine = _mock.busLines.find(bl => bl.id == busLineId); - if (!busLine) { - return res.status(404).json({ error: 'bus_line_not_found' }); + const schedule = result.rows[0]; + const departureTimes = calculateDepartureTimes( + startTime, + busLineId, + _mock, + ); + + const weekdaysResult = parseWeekdaysValue(schedule.weekdays); + + res.status(201).json({ + schedule: { + id: Number(schedule.id), + busLineId: Number(schedule.bus_line_id), + busId: Number(schedule.bus_id), + driverId: + schedule.driver_id !== null && schedule.driver_id !== undefined + ? Number(schedule.driver_id) + : null, + startTime: schedule.start_time, + endTime: schedule.end_time, + weekdays: weekdaysResult, + departureTimes, + status: schedule.status, + createdAt: schedule.created_at, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); + +app.put( + "/admin/schedules/:scheduleId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { scheduleId } = req.params; + const { busLineId, busId, driverId, startTime, weekdays } = req.body; + + if (!scheduleId || !Number.isFinite(Number(scheduleId))) { + return res.status(400).json({ error: "invalid_schedule_id" }); + } + + if (!busLineId || !busId || !startTime) { + return res.status(400).json({ + error: + "missing_required_fields: busLineId, busId, startTime required", + }); + } + + const scheduleWeekdays = + Array.isArray(weekdays) && weekdays.length > 0 + ? weekdays.map((w) => Number(w)).filter((w) => w >= 0 && w <= 6) + : []; + + if (DEV_MOCK) { + const index = (_mock.schedules || []).findIndex( + (s) => Number(s.id) === Number(scheduleId), + ); + if (index === -1) { + return res.status(404).json({ error: "schedule_not_found" }); + } + + const busLine = _mock.busLines.find((bl) => bl.id == busLineId); + if (!busLine) { + return res.status(404).json({ error: "bus_line_not_found" }); + } + + const startDate = new Date(startTime); + const endDate = new Date(startDate); + endDate.setMinutes( + endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60), + ); + + _mock.schedules[index] = { + ..._mock.schedules[index], + busLineId: Number(busLineId), + busId: Number(busId), + driverId: driverId ? Number(driverId) : null, + startTime, + endTime: endDate.toISOString(), + weekdays: scheduleWeekdays, + }; + + return res.json({ schedule: _mock.schedules[index] }); + } + + const existingResult = await db.query( + ` + SELECT id, status + FROM transit.schedules + WHERE id = $1 + `, + [scheduleId], + ); + + if (existingResult.rows.length === 0) { + return res.status(404).json({ error: "schedule_not_found" }); + } + + const busLine = await db.query( + "SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1", + [busLineId], + ); + if (!busLine.rows[0]) { + return res.status(404).json({ error: "bus_line_not_found" }); } const startDate = new Date(startTime); const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60)); + endDate.setMinutes( + endDate.getMinutes() + + (busLine.rows[0].estimated_duration_minutes || 60), + ); - _mock.schedules[index] = { - ..._mock.schedules[index], - busLineId: Number(busLineId), - busId: Number(busId), - driverId: driverId ? Number(driverId) : null, - startTime, - endTime: endDate.toISOString(), - weekdays: scheduleWeekdays, - }; - - return res.json({ schedule: _mock.schedules[index] }); - } - - const existingResult = await db.query(` - SELECT id, status - FROM transit.schedules - WHERE id = $1 - `, [scheduleId]); - - if (existingResult.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); - } - - const busLine = await db.query('SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1', [busLineId]); - if (!busLine.rows[0]) { - return res.status(404).json({ error: 'bus_line_not_found' }); - } - - const startDate = new Date(startTime); - const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + (busLine.rows[0].estimated_duration_minutes || 60)); - - const hasWeekdaysColumn = await db.query(` + const hasWeekdaysColumn = await db.query(` SELECT 1 FROM information_schema.columns WHERE table_schema='transit' AND table_name='schedules' AND column_name='weekdays' `); - let result; - if (hasWeekdaysColumn.rows.length > 0) { - result = await db.query(` + let result; + if (hasWeekdaysColumn.rows.length > 0) { + result = await db.query( + ` UPDATE transit.schedules SET bus_line_id = $2, @@ -2435,9 +3457,20 @@ app.put('/admin/schedules/:scheduleId', authMiddleware, adminMiddleware, async ( updated_at = NOW() WHERE id = $1 RETURNING id, bus_line_id, bus_id, driver_id, start_time, end_time, status, weekdays, created_at - `, [scheduleId, busLineId, busId, driverId || null, startTime, endDate.toISOString(), JSON.stringify(scheduleWeekdays)]); - } else { - result = await db.query(` + `, + [ + scheduleId, + busLineId, + busId, + driverId || null, + startTime, + endDate.toISOString(), + JSON.stringify(scheduleWeekdays), + ], + ); + } else { + result = await db.query( + ` UPDATE transit.schedules SET bus_line_id = $2, @@ -2448,117 +3481,164 @@ app.put('/admin/schedules/:scheduleId', authMiddleware, adminMiddleware, async ( updated_at = NOW() WHERE id = $1 RETURNING id, bus_line_id, bus_id, driver_id, start_time, end_time, status, created_at - `, [scheduleId, busLineId, busId, driverId || null, startTime, endDate.toISOString()]); - } - - const schedule = result.rows[0]; - return res.json({ - schedule: { - id: Number(schedule.id), - busLineId: Number(schedule.bus_line_id), - busId: Number(schedule.bus_id), - driverId: schedule.driver_id !== null && schedule.driver_id !== undefined ? Number(schedule.driver_id) : null, - startTime: schedule.start_time, - endTime: schedule.end_time, - weekdays: parseWeekdaysValue(schedule.weekdays), - status: schedule.status, - createdAt: schedule.created_at, - }, - }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); - -app.post('/admin/schedules/bulk', authMiddleware, adminMiddleware, async (req, res) => { - const requestedSchedules = Array.isArray(req.body?.schedules) ? req.body.schedules : []; - - if (requestedSchedules.length === 0) { - return res.status(400).json({ error: 'missing_required_fields: schedules array required' }); - } - - const normalizedSchedules = requestedSchedules.map((schedule, index) => { - const busLineId = Number(schedule?.busLineId); - const busId = Number(schedule?.busId); - const driverId = schedule?.driverId !== null && schedule?.driverId !== undefined && schedule?.driverId !== '' - ? Number(schedule.driverId) - : null; - const startTime = schedule?.startTime ? String(schedule.startTime) : ''; - const weekdays = Array.isArray(schedule?.weekdays) && schedule.weekdays.length > 0 - ? schedule.weekdays.map(day => Number(day)).filter(day => day >= 0 && day <= 6) - : []; - - if (!Number.isFinite(busLineId) || !Number.isFinite(busId) || !startTime) { - throw new Error(`invalid_schedule_entry:${index}`); - } - - return { busLineId, busId, driverId, startTime, weekdays }; - }); - - try { - if (DEV_MOCK) { - if (!_mock.schedules) _mock.schedules = []; - - const createdSchedules = []; - - for (const entry of normalizedSchedules) { - const busLine = _mock.busLines.find(bl => bl.id == entry.busLineId); - if (!busLine) { - return res.status(404).json({ error: 'bus_line_not_found' }); - } - - const startDate = new Date(entry.startTime); - const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60)); - - const departureTimes = calculateDepartureTimes(entry.startTime, Number(entry.busLineId), _mock); - - const schedule = { - id: (_mock.nextScheduleId || 1), - busLineId: entry.busLineId, - busId: entry.busId, - driverId: entry.driverId, - startTime: entry.startTime, - endTime: endDate.toISOString(), - weekdays: entry.weekdays, - departureTimes, - status: 'planned', - createdAt: new Date().toISOString(), - }; - - _mock.nextScheduleId = ((_mock.nextScheduleId || 1) + 1); - _mock.schedules.push(schedule); - createdSchedules.push(schedule); + `, + [ + scheduleId, + busLineId, + busId, + driverId || null, + startTime, + endDate.toISOString(), + ], + ); } - return res.status(201).json({ createdCount: createdSchedules.length, schedules: createdSchedules }); + const schedule = result.rows[0]; + return res.json({ + schedule: { + id: Number(schedule.id), + busLineId: Number(schedule.bus_line_id), + busId: Number(schedule.bus_id), + driverId: + schedule.driver_id !== null && schedule.driver_id !== undefined + ? Number(schedule.driver_id) + : null, + startTime: schedule.start_time, + endTime: schedule.end_time, + weekdays: parseWeekdaysValue(schedule.weekdays), + status: schedule.status, + createdAt: schedule.created_at, + }, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); + +app.post( + "/admin/schedules/bulk", + authMiddleware, + adminMiddleware, + async (req, res) => { + const requestedSchedules = Array.isArray(req.body?.schedules) + ? req.body.schedules + : []; + + if (requestedSchedules.length === 0) { + return res + .status(400) + .json({ error: "missing_required_fields: schedules array required" }); } - const dbClient = await db.pool.connect(); - try { - await dbClient.query('BEGIN'); + const normalizedSchedules = requestedSchedules.map((schedule, index) => { + const busLineId = Number(schedule?.busLineId); + const busId = Number(schedule?.busId); + const driverId = + schedule?.driverId !== null && + schedule?.driverId !== undefined && + schedule?.driverId !== "" + ? Number(schedule.driverId) + : null; + const startTime = schedule?.startTime ? String(schedule.startTime) : ""; + const weekdays = + Array.isArray(schedule?.weekdays) && schedule.weekdays.length > 0 + ? schedule.weekdays + .map((day) => Number(day)) + .filter((day) => day >= 0 && day <= 6) + : []; - const hasWeekdaysColumn = await dbClient.query(` + if ( + !Number.isFinite(busLineId) || + !Number.isFinite(busId) || + !startTime + ) { + throw new Error(`invalid_schedule_entry:${index}`); + } + + return { busLineId, busId, driverId, startTime, weekdays }; + }); + + try { + if (DEV_MOCK) { + if (!_mock.schedules) _mock.schedules = []; + + const createdSchedules = []; + + for (const entry of normalizedSchedules) { + const busLine = _mock.busLines.find((bl) => bl.id == entry.busLineId); + if (!busLine) { + return res.status(404).json({ error: "bus_line_not_found" }); + } + + const startDate = new Date(entry.startTime); + const endDate = new Date(startDate); + endDate.setMinutes( + endDate.getMinutes() + (busLine.estimatedDurationMinutes || 60), + ); + + const departureTimes = calculateDepartureTimes( + entry.startTime, + Number(entry.busLineId), + _mock, + ); + + const schedule = { + id: _mock.nextScheduleId || 1, + busLineId: entry.busLineId, + busId: entry.busId, + driverId: entry.driverId, + startTime: entry.startTime, + endTime: endDate.toISOString(), + weekdays: entry.weekdays, + departureTimes, + status: "planned", + createdAt: new Date().toISOString(), + }; + + _mock.nextScheduleId = (_mock.nextScheduleId || 1) + 1; + _mock.schedules.push(schedule); + createdSchedules.push(schedule); + } + + return res.status(201).json({ + createdCount: createdSchedules.length, + schedules: createdSchedules, + }); + } + + const dbClient = await db.pool.connect(); + try { + await dbClient.query("BEGIN"); + + const hasWeekdaysColumn = await dbClient.query(` SELECT 1 FROM information_schema.columns WHERE table_schema='transit' AND table_name='schedules' AND column_name='weekdays' `); - const createdSchedules = []; + const createdSchedules = []; - for (const entry of normalizedSchedules) { - const busLine = await dbClient.query('SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1', [entry.busLineId]); - if (!busLine.rows[0]) { - throw new Error('bus_line_not_found'); - } + for (const entry of normalizedSchedules) { + const busLine = await dbClient.query( + "SELECT estimated_duration_minutes FROM transit.bus_lines WHERE id = $1", + [entry.busLineId], + ); + if (!busLine.rows[0]) { + throw new Error("bus_line_not_found"); + } - const startDate = new Date(entry.startTime); - const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + (busLine.rows[0].estimated_duration_minutes || 60)); + const startDate = new Date(entry.startTime); + const endDate = new Date(startDate); + endDate.setMinutes( + endDate.getMinutes() + + (busLine.rows[0].estimated_duration_minutes || 60), + ); - let result; - if (hasWeekdaysColumn.rows.length > 0) { - result = await dbClient.query(` + let result; + if (hasWeekdaysColumn.rows.length > 0) { + result = await dbClient.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules ) @@ -2566,9 +3646,19 @@ app.post('/admin/schedules/bulk', authMiddleware, adminMiddleware, async (req, r SELECT next_id.id, $1, $2, $3, $4, $5, 'planned', $6 FROM next_id RETURNING id, bus_line_id, bus_id, driver_id, start_time, end_time, status, weekdays, created_at - `, [entry.busLineId, entry.busId, entry.driverId, entry.startTime, endDate.toISOString(), JSON.stringify(entry.weekdays)]); - } else { - result = await dbClient.query(` + `, + [ + entry.busLineId, + entry.busId, + entry.driverId, + entry.startTime, + endDate.toISOString(), + JSON.stringify(entry.weekdays), + ], + ); + } else { + result = await dbClient.query( + ` WITH next_id AS ( SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules ) @@ -2576,83 +3666,115 @@ app.post('/admin/schedules/bulk', authMiddleware, adminMiddleware, async (req, r SELECT next_id.id, $1, $2, $3, $4, $5, 'planned' FROM next_id RETURNING id, bus_line_id, bus_id, driver_id, start_time, end_time, status, created_at - `, [entry.busLineId, entry.busId, entry.driverId, entry.startTime, endDate.toISOString()]); + `, + [ + entry.busLineId, + entry.busId, + entry.driverId, + entry.startTime, + endDate.toISOString(), + ], + ); + } + + const schedule = result.rows[0]; + createdSchedules.push({ + id: Number(schedule.id), + busLineId: Number(schedule.bus_line_id), + busId: Number(schedule.bus_id), + driverId: + schedule.driver_id !== null && schedule.driver_id !== undefined + ? Number(schedule.driver_id) + : null, + startTime: schedule.start_time, + endTime: schedule.end_time, + weekdays: parseWeekdaysValue(schedule.weekdays), + status: schedule.status, + createdAt: schedule.created_at, + }); } - const schedule = result.rows[0]; - createdSchedules.push({ - id: Number(schedule.id), - busLineId: Number(schedule.bus_line_id), - busId: Number(schedule.bus_id), - driverId: schedule.driver_id !== null && schedule.driver_id !== undefined ? Number(schedule.driver_id) : null, - startTime: schedule.start_time, - endTime: schedule.end_time, - weekdays: parseWeekdaysValue(schedule.weekdays), - status: schedule.status, - createdAt: schedule.created_at, + await dbClient.query("COMMIT"); + return res.status(201).json({ + createdCount: createdSchedules.length, + schedules: createdSchedules, }); + } catch (err) { + await dbClient.query("ROLLBACK"); + throw err; + } finally { + dbClient.release(); } - - await dbClient.query('COMMIT'); - return res.status(201).json({ createdCount: createdSchedules.length, schedules: createdSchedules }); } catch (err) { - await dbClient.query('ROLLBACK'); - throw err; - } finally { - dbClient.release(); + console.error(err); + const errorMessage = + err.message === "bus_line_not_found" + ? "bus_line_not_found" + : err.message.startsWith("invalid_schedule_entry:") + ? err.message + : "internal_error"; + const statusCode = + errorMessage === "bus_line_not_found" + ? 404 + : errorMessage.startsWith("invalid_schedule_entry:") + ? 400 + : 500; + res + .status(statusCode) + .json({ error: errorMessage, details: err.message }); } - } catch (err) { - console.error(err); - const errorMessage = err.message === 'bus_line_not_found' - ? 'bus_line_not_found' - : err.message.startsWith('invalid_schedule_entry:') - ? err.message - : 'internal_error'; - const statusCode = errorMessage === 'bus_line_not_found' ? 404 : errorMessage.startsWith('invalid_schedule_entry:') ? 400 : 500; - res.status(statusCode).json({ error: errorMessage, details: err.message }); - } -}); + }, +); -app.delete('/admin/schedules/:scheduleId', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { scheduleId } = req.params; +app.delete( + "/admin/schedules/:scheduleId", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { scheduleId } = req.params; - if (!scheduleId || !Number.isFinite(Number(scheduleId))) { - return res.status(400).json({ error: 'invalid_schedule_id' }); - } - - if (DEV_MOCK) { - if (!_mock.schedules) _mock.schedules = []; - const index = _mock.schedules.findIndex(s => Number(s.id) === Number(scheduleId)); - if (index === -1) { - return res.status(404).json({ error: 'schedule_not_found' }); + if (!scheduleId || !Number.isFinite(Number(scheduleId))) { + return res.status(400).json({ error: "invalid_schedule_id" }); } - const deleted = _mock.schedules.splice(index, 1)[0]; - return res.json({ success: true, schedule: deleted }); + + if (DEV_MOCK) { + if (!_mock.schedules) _mock.schedules = []; + const index = _mock.schedules.findIndex( + (s) => Number(s.id) === Number(scheduleId), + ); + if (index === -1) { + return res.status(404).json({ error: "schedule_not_found" }); + } + const deleted = _mock.schedules.splice(index, 1)[0]; + return res.json({ success: true, schedule: deleted }); + } + + const result = await db.query( + `DELETE FROM transit.schedules WHERE id = $1 RETURNING id`, + [scheduleId], + ); + + if (result.rows.length === 0) { + return res.status(404).json({ error: "schedule_not_found" }); + } + + res.json({ success: true, schedule: { id: Number(result.rows[0].id) } }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "internal_error", details: err.message }); } - - const result = await db.query( - `DELETE FROM transit.schedules WHERE id = $1 RETURNING id`, - [scheduleId] - ); - - if (result.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); - } - - res.json({ success: true, schedule: { id: Number(result.rows[0].id) } }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + }, +); // ============================================ // PUBLIC - Get schedules (for public timetable) // ============================================ -app.get('/schedules', async (req, res) => { +app.get("/schedules", async (req, res) => { try { - const filters = parseScheduleFilters(req.query, { statuses: ['planned', 'active'] }); + const filters = parseScheduleFilters(req.query, { + statuses: ["planned", "active"], + }); if (DEV_MOCK) { const routeStopsByLine = new Map(); @@ -2667,23 +3789,25 @@ app.get('/schedules', async (req, res) => { }); } - const schedules = expandRecurringScheduleRows((_mock.schedules || []).map(s => { - const busLine = _mock.busLines.find(bl => bl.id === s.busLineId); - const bus = _mock.fleetBuses.find(b => b.id === s.busId); - return { - id: s.id, - busLineId: s.busLineId, - busId: s.busId, - startTime: s.startTime, - endTime: s.endTime, - weekdays: s.weekdays || [], - status: s.status, - lineNumber: busLine?.lineNumber || 0, - busName: bus?.name || 'Unknown Bus', - driverId: null, - estimatedDurationMinutes: busLine?.estimatedDurationMinutes || 60, - }; - })).filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)); + const schedules = expandRecurringScheduleRows( + (_mock.schedules || []).map((s) => { + const busLine = _mock.busLines.find((bl) => bl.id === s.busLineId); + const bus = _mock.fleetBuses.find((b) => b.id === s.busId); + return { + id: s.id, + busLineId: s.busLineId, + busId: s.busId, + startTime: s.startTime, + endTime: s.endTime, + weekdays: s.weekdays || [], + status: s.status, + lineNumber: busLine?.lineNumber || 0, + busName: bus?.name || "Unknown Bus", + driverId: null, + estimatedDurationMinutes: busLine?.estimatedDurationMinutes || 60, + }; + }), + ).filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)); return res.json({ schedules }); } @@ -2733,11 +3857,18 @@ app.get('/schedules', async (req, res) => { const result = await db.query(query, params); - const lineIds = Array.from(new Set(result.rows.map(row => Number(row.bus_line_id)).filter(Number.isFinite))); + const lineIds = Array.from( + new Set( + result.rows + .map((row) => Number(row.bus_line_id)) + .filter(Number.isFinite), + ), + ); const routeStopsByLine = new Map(); if (filters.stopId !== null && lineIds.length > 0) { - const routeStopsResult = await db.query(` + const routeStopsResult = await db.query( + ` SELECT rs.bus_line_id, rs.stop_id, @@ -2745,7 +3876,9 @@ app.get('/schedules', async (req, res) => { FROM transit.route_stops rs WHERE rs.bus_line_id = ANY($1::int[]) ORDER BY rs.bus_line_id ASC, rs.stop_order ASC - `, [lineIds]); + `, + [lineIds], + ); for (const row of routeStopsResult.rows) { const busLineId = Number(row.bus_line_id); @@ -2757,8 +3890,8 @@ app.get('/schedules', async (req, res) => { } const schedules = expandRecurringScheduleRows(result.rows) - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)) - .map(row => ({ + .filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)) + .map((row) => ({ id: Number(row.id), busLineId: Number(row.bus_line_id), busId: Number(row.bus_id), @@ -2766,11 +3899,16 @@ app.get('/schedules', async (req, res) => { driverId: null, startTime: row.start_time, endTime: row.end_time, - weekdays: row.weekdays ? (typeof row.weekdays === 'string' ? JSON.parse(row.weekdays) : row.weekdays) : [], + weekdays: row.weekdays + ? typeof row.weekdays === "string" + ? JSON.parse(row.weekdays) + : row.weekdays + : [], status: row.status, lineNumber: row.line_number, busName: row.bus_name, - estimatedDurationMinutes: Number(row.estimated_duration_minutes) || null, + estimatedDurationMinutes: + Number(row.estimated_duration_minutes) || null, isRecurringInstance: row.is_recurring_instance || false, templateId: row.template_id || null, })); @@ -2778,13 +3916,15 @@ app.get('/schedules', async (req, res) => { res.json({ schedules }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); -app.get('/schedules/overview', async (req, res) => { +app.get("/schedules/overview", async (req, res) => { try { - const filters = parseScheduleFilters(req.query, { statuses: ['planned', 'active'] }); + const filters = parseScheduleFilters(req.query, { + statuses: ["planned", "active"], + }); const overviewDateKey = filters.date || null; if (DEV_MOCK) { @@ -2801,14 +3941,20 @@ app.get('/schedules/overview', async (req, res) => { stop_id: routeStop.stopId, stop_order: routeStop.stopOrder, estimated_arrival_minutes: routeStop.estimatedArrivalMinutes, - stop_name: routeStop.name || '', + stop_name: routeStop.name || "", }); } - const baseRows = (_mock.schedules || []).map(schedule => { - const busLine = _mock.busLines.find(line => Number(line.id) === Number(schedule.busLineId)); - const bus = _mock.fleetBuses.find(vehicle => Number(vehicle.id) === Number(schedule.busId)); - const driver = _mock.drivers.find(person => Number(person.id) === Number(schedule.driverId)); + const baseRows = (_mock.schedules || []).map((schedule) => { + const busLine = _mock.busLines.find( + (line) => Number(line.id) === Number(schedule.busLineId), + ); + const bus = _mock.fleetBuses.find( + (vehicle) => Number(vehicle.id) === Number(schedule.busId), + ); + const driver = _mock.drivers.find( + (person) => Number(person.id) === Number(schedule.driverId), + ); return { id: schedule.id, @@ -2821,21 +3967,28 @@ app.get('/schedules/overview', async (req, res) => { weekdays: schedule.weekdays || [], status: schedule.status, lineNumber: busLine?.lineNumber || 0, - busName: bus?.name || 'Unknown Bus', - licensePlate: bus?.licensePlate || '', + busName: bus?.name || "Unknown Bus", + licensePlate: bus?.licensePlate || "", createdAt: schedule.createdAt, }; }); - const schedules = mapRecurringOccurrenceRows(baseRows, overviewDateKey, routeStopsByLine) - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)); + const schedules = mapRecurringOccurrenceRows( + baseRows, + overviewDateKey, + routeStopsByLine, + ).filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)); return res.json({ date: overviewDateKey, summary: { total: schedules.length, - recurring: schedules.filter(schedule => schedule.scheduleType === 'recurring').length, - oneTime: schedules.filter(schedule => schedule.scheduleType === 'one_time').length, + recurring: schedules.filter( + (schedule) => schedule.scheduleType === "recurring", + ).length, + oneTime: schedules.filter( + (schedule) => schedule.scheduleType === "one_time", + ).length, }, schedules, }); @@ -2862,34 +4015,40 @@ app.get('/schedules/overview', async (req, res) => { b.license_plate AS "licensePlate", TRIM(COALESCE(e.firstname, '') || ' ' || COALESCE(e.lastname, '')) AS "driverName"`; - const result = hasWeekdaysColumn.rows.length > 0 - ? await db.query(`${baseSelect}, s.weekdays AS weekdays + const result = + hasWeekdaysColumn.rows.length > 0 + ? await db.query( + `${baseSelect}, s.weekdays AS weekdays FROM transit.schedules s LEFT JOIN transit.bus_lines bl ON s.bus_line_id = bl.id LEFT JOIN fleet.bus b ON s.bus_id = b.id LEFT JOIN workers.employees e ON s.driver_id = e.id WHERE s.status = ANY($1::text[]) - ORDER BY s.start_time ASC`, [filters.statuses]) - : await db.query(`${baseSelect}, NULL AS weekdays + ORDER BY s.start_time ASC`, + [filters.statuses], + ) + : await db.query( + `${baseSelect}, NULL AS weekdays FROM transit.schedules s LEFT JOIN transit.bus_lines bl ON s.bus_line_id = bl.id LEFT JOIN fleet.bus b ON s.bus_id = b.id LEFT JOIN workers.employees e ON s.driver_id = e.id WHERE s.status = ANY($1::text[]) - ORDER BY s.start_time ASC`, [filters.statuses]); + ORDER BY s.start_time ASC`, + [filters.statuses], + ); const lineIds = Array.from( new Set( - result.rows - .map(row => Number(row.busLineId)) - .filter(Number.isFinite) - ) + result.rows.map((row) => Number(row.busLineId)).filter(Number.isFinite), + ), ); const routeStopsByLine = new Map(); if (lineIds.length > 0) { - const routeStopsResult = await db.query(` + const routeStopsResult = await db.query( + ` SELECT rs.bus_line_id, rs.stop_id, @@ -2900,7 +4059,9 @@ app.get('/schedules/overview', async (req, res) => { JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = ANY($1::int[]) ORDER BY rs.bus_line_id ASC, rs.stop_order ASC - `, [lineIds]); + `, + [lineIds], + ); for (const row of routeStopsResult.rows) { const busLineId = Number(row.bus_line_id); @@ -2914,25 +4075,41 @@ app.get('/schedules/overview', async (req, res) => { let schedules = []; if (filters.fromDate && filters.toDate) { - schedules = materializeScheduleRowsInRange(result.rows, filters.fromDate, filters.toDate, routeStopsByLine) - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)); + schedules = materializeScheduleRowsInRange( + result.rows, + filters.fromDate, + filters.toDate, + routeStopsByLine, + ).filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)); } else { - schedules = mapRecurringOccurrenceRows(result.rows, overviewDateKey, routeStopsByLine) - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)); + schedules = mapRecurringOccurrenceRows( + result.rows, + overviewDateKey, + routeStopsByLine, + ).filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)); } // Enrich schedules with live progress for the occurrence date(s) if (!DEV_MOCK && schedules.length > 0) { - const scheduleIds = [...new Set(schedules.map(s => Number(s.id)).filter(Number.isFinite))]; - const occurrenceDates = [...new Set(schedules.map(s => toDateKey(s.startTime)).filter(Boolean))]; + const scheduleIds = [ + ...new Set(schedules.map((s) => Number(s.id)).filter(Number.isFinite)), + ]; + const occurrenceDates = [ + ...new Set( + schedules.map((s) => toDateKey(s.startTime)).filter(Boolean), + ), + ]; if (scheduleIds.length > 0 && occurrenceDates.length > 0) { - const progressRes = await db.query(` + const progressRes = await db.query( + ` SELECT schedule_id, scheduled_passed_date, stop_order, actual_passed_at, scheduled_passed_at, delay_minutes FROM transit.schedule_stop_progress WHERE schedule_id = ANY($1::int[]) AND scheduled_passed_date = ANY($2::date[]) ORDER BY schedule_id ASC, scheduled_passed_date ASC, actual_passed_at ASC - `, [scheduleIds, occurrenceDates]); + `, + [scheduleIds, occurrenceDates], + ); const progressByKey = new Map(); for (const row of progressRes.rows) { @@ -2941,20 +4118,33 @@ app.get('/schedules/overview', async (req, res) => { progressByKey.get(key).push(row); } - schedules = schedules.map(s => { + schedules = schedules.map((s) => { const key = `${s.id}|${toDateKey(s.startTime)}`; const progRows = progressByKey.get(key) || []; - const totalStops = (routeStopsByLine.get(Number(s.busLineId)) || []).length; + const totalStops = (routeStopsByLine.get(Number(s.busLineId)) || []) + .length; const passedStops = progRows.length; let currentDelayMinutes = null; if (progRows.length > 0) { const latest = progRows[progRows.length - 1]; - currentDelayMinutes = Number(latest.delay_minutes ?? latest.delay_minutes) || null; + currentDelayMinutes = + Number(latest.delay_minutes ?? latest.delay_minutes) || null; } - const statusOverride = passedStops >= totalStops && totalStops > 0 ? 'completed' : (passedStops > 0 ? 'in_progress' : s.status); + const statusOverride = + passedStops >= totalStops && totalStops > 0 + ? "completed" + : passedStops > 0 + ? "in_progress" + : s.status; - return { ...s, status: statusOverride, passedStops, totalStops, currentDelayMinutes }; + return { + ...s, + status: statusOverride, + passedStops, + totalStops, + currentDelayMinutes, + }; }); } } else if (DEV_MOCK && schedules.length > 0) { @@ -2966,15 +4156,30 @@ app.get('/schedules/overview', async (req, res) => { progressByKey.get(key).push(p); } - schedules = schedules.map(s => { + schedules = schedules.map((s) => { const key = `${s.id}|${toDateKey(s.startTime)}`; const progRows = progressByKey.get(key) || []; - const totalStops = (routeStopsByLine.get(Number(s.busLineId)) || []).length; + const totalStops = (routeStopsByLine.get(Number(s.busLineId)) || []) + .length; const passedStops = progRows.length; - const latest = progRows.length > 0 ? progRows[progRows.length - 1] : null; - const currentDelayMinutes = latest ? Number(latest.delayMinutes) || 0 : null; - const statusOverride = passedStops >= totalStops && totalStops > 0 ? 'completed' : (passedStops > 0 ? 'in_progress' : s.status); - return { ...s, status: statusOverride, passedStops, totalStops, currentDelayMinutes }; + const latest = + progRows.length > 0 ? progRows[progRows.length - 1] : null; + const currentDelayMinutes = latest + ? Number(latest.delayMinutes) || 0 + : null; + const statusOverride = + passedStops >= totalStops && totalStops > 0 + ? "completed" + : passedStops > 0 + ? "in_progress" + : s.status; + return { + ...s, + status: statusOverride, + passedStops, + totalStops, + currentDelayMinutes, + }; }); } @@ -2982,21 +4187,25 @@ app.get('/schedules/overview', async (req, res) => { date: overviewDateKey, summary: { total: schedules.length, - recurring: schedules.filter(schedule => schedule.scheduleType === 'recurring').length, - oneTime: schedules.filter(schedule => schedule.scheduleType === 'one_time').length, + recurring: schedules.filter( + (schedule) => schedule.scheduleType === "recurring", + ).length, + oneTime: schedules.filter( + (schedule) => schedule.scheduleType === "one_time", + ).length, }, schedules, }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // PUBLIC - Get all recurring schedule templates (no date filtering) // ============================================ -app.get('/schedules/recurring', async (req, res) => { +app.get("/schedules/recurring", async (req, res) => { try { const filters = parseScheduleFilters(req.query); @@ -3014,16 +4223,25 @@ app.get('/schedules/recurring', async (req, res) => { stop_id: routeStop.stopId, stop_order: routeStop.stopOrder, estimated_arrival_minutes: routeStop.estimatedArrivalMinutes, - stop_name: routeStop.name || '', + stop_name: routeStop.name || "", }); } const baseRows = (_mock.schedules || []) - .filter(schedule => Array.isArray(schedule.weekdays) && schedule.weekdays.length > 0) - .map(schedule => { - const busLine = _mock.busLines.find(line => Number(line.id) === Number(schedule.busLineId)); - const bus = _mock.fleetBuses.find(vehicle => Number(vehicle.id) === Number(schedule.busId)); - const driver = _mock.drivers.find(person => Number(person.id) === Number(schedule.driverId)); + .filter( + (schedule) => + Array.isArray(schedule.weekdays) && schedule.weekdays.length > 0, + ) + .map((schedule) => { + const busLine = _mock.busLines.find( + (line) => Number(line.id) === Number(schedule.busLineId), + ); + const bus = _mock.fleetBuses.find( + (vehicle) => Number(vehicle.id) === Number(schedule.busId), + ); + const driver = _mock.drivers.find( + (person) => Number(person.id) === Number(schedule.driverId), + ); return { id: schedule.id, @@ -3035,16 +4253,20 @@ app.get('/schedules/recurring', async (req, res) => { endTime: schedule.endTime, weekdays: schedule.weekdays || [], lineNumber: busLine?.lineNumber || 0, - busName: bus?.name || 'Unknown Bus', - licensePlate: bus?.licensePlate || '', + busName: bus?.name || "Unknown Bus", + licensePlate: bus?.licensePlate || "", createdAt: schedule.createdAt, }; }); const schedules = baseRows - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)) - .map(row => { - const materialized = materializeScheduleOverviewRow(row, null, routeStopsByLine); + .filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)) + .map((row) => { + const materialized = materializeScheduleOverviewRow( + row, + null, + routeStopsByLine, + ); return { ...materialized, status: undefined, @@ -3084,11 +4306,16 @@ app.get('/schedules/recurring', async (req, res) => { WHERE s.weekdays IS NOT NULL `); - const lineIds = Array.from(new Set(result.rows.map(row => Number(row.busLineId)).filter(Number.isFinite))); + const lineIds = Array.from( + new Set( + result.rows.map((row) => Number(row.busLineId)).filter(Number.isFinite), + ), + ); const routeStopsByLine = new Map(); if (lineIds.length > 0) { - const routeStopsResult = await db.query(` + const routeStopsResult = await db.query( + ` SELECT rs.bus_line_id, rs.stop_id, @@ -3099,7 +4326,9 @@ app.get('/schedules/recurring', async (req, res) => { JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = ANY($1::int[]) ORDER BY rs.bus_line_id ASC, rs.stop_order ASC - `, [lineIds]); + `, + [lineIds], + ); for (const row of routeStopsResult.rows) { const busLineId = Number(row.bus_line_id); @@ -3111,9 +4340,13 @@ app.get('/schedules/recurring', async (req, res) => { } const schedules = result.rows - .filter(row => scheduleMatchesFilters(row, filters, routeStopsByLine)) - .map(row => { - const schedule = materializeScheduleOverviewRow(row, null, routeStopsByLine); + .filter((row) => scheduleMatchesFilters(row, filters, routeStopsByLine)) + .map((row) => { + const schedule = materializeScheduleOverviewRow( + row, + null, + routeStopsByLine, + ); delete schedule.status; return schedule; }); @@ -3121,24 +4354,26 @@ app.get('/schedules/recurring', async (req, res) => { res.json({ schedules }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); -app.get('/schedules/:scheduleId', async (req, res) => { +app.get("/schedules/:scheduleId", async (req, res) => { try { const { scheduleId } = req.params; if (DEV_MOCK) { - const schedule = (_mock.schedules || []).find(s => Number(s.id) === Number(scheduleId)); + const schedule = (_mock.schedules || []).find( + (s) => Number(s.id) === Number(scheduleId), + ); if (!schedule) { - return res.status(404).json({ error: 'schedule_not_found' }); + return res.status(404).json({ error: "schedule_not_found" }); } - + // Enrich with bus line and fleet bus info - const busLine = _mock.busLines.find(bl => bl.id === schedule.busLineId); - const bus = _mock.fleetBuses.find(b => b.id === schedule.busId); - + const busLine = _mock.busLines.find((bl) => bl.id === schedule.busLineId); + const bus = _mock.fleetBuses.find((b) => b.id === schedule.busId); + return res.json({ schedule: { id: schedule.id, @@ -3149,12 +4384,13 @@ app.get('/schedules/:scheduleId', async (req, res) => { weekdays: schedule.weekdays || [], status: schedule.status, lineNumber: busLine?.lineNumber || 0, - busName: bus?.name || 'Unknown Bus', + busName: bus?.name || "Unknown Bus", }, }); } - const result = await db.query(` + const result = await db.query( + ` SELECT s.id, s.bus_line_id, @@ -3172,15 +4408,17 @@ app.get('/schedules/:scheduleId', async (req, res) => { LEFT JOIN transit.bus_lines bl ON s.bus_line_id = bl.id LEFT JOIN fleet.bus b ON s.bus_id = b.id WHERE s.id = $1 - `, [scheduleId]); + `, + [scheduleId], + ); if (result.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); + return res.status(404).json({ error: "schedule_not_found" }); } const row = result.rows[0]; const weekdays = parseWeekdaysValue(row.weekdays); - + res.json({ schedule: { id: Number(row.id), @@ -3199,33 +4437,37 @@ app.get('/schedules/:scheduleId', async (req, res) => { }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // PUBLIC - Get stops for a schedule's line // ============================================ -app.get('/schedules/:scheduleId/stops', async (req, res) => { +app.get("/schedules/:scheduleId/stops", async (req, res) => { try { const { scheduleId } = req.params; if (DEV_MOCK) { - const schedule = (_mock.schedules || []).find(s => Number(s.id) === Number(scheduleId)); + const schedule = (_mock.schedules || []).find( + (s) => Number(s.id) === Number(scheduleId), + ); if (!schedule) { - return res.status(404).json({ error: 'schedule_not_found' }); + return res.status(404).json({ error: "schedule_not_found" }); } const routeStops = (_mock.routeStops || []) - .filter(rs => Number(rs.busLineId) === Number(schedule.busLineId)) + .filter((rs) => Number(rs.busLineId) === Number(schedule.busLineId)) .sort((a, b) => a.stopOrder - b.stopOrder); - + // Map to match database response format - const formattedStops = routeStops.map(rs => { - const stop = (_mock.stops || []).find(s => Number(s.id) === Number(rs.stopId)); + const formattedStops = routeStops.map((rs) => { + const stop = (_mock.stops || []).find( + (s) => Number(s.id) === Number(rs.stopId), + ); return { id: rs.id, order: rs.stopOrder, - name: stop?.name || rs.name || 'Unknown Stop', + name: stop?.name || rs.name || "Unknown Stop", latitude: stop?.latitude ?? rs.latitude ?? 0, longitude: stop?.longitude ?? rs.longitude ?? 0, estimatedArrivalMinutes: rs.estimatedArrivalMinutes || 0, @@ -3236,16 +4478,17 @@ app.get('/schedules/:scheduleId/stops', async (req, res) => { const scheduleResult = await db.query( `SELECT bus_line_id FROM transit.schedules WHERE id = $1`, - [scheduleId] + [scheduleId], ); if (scheduleResult.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); + return res.status(404).json({ error: "schedule_not_found" }); } const busLineId = scheduleResult.rows[0].bus_line_id; - const stopsResult = await db.query(` + const stopsResult = await db.query( + ` SELECT rs.id, rs.stop_order, @@ -3257,10 +4500,12 @@ app.get('/schedules/:scheduleId/stops', async (req, res) => { JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = $1 ORDER BY rs.stop_order ASC - `, [busLineId]); + `, + [busLineId], + ); res.json({ - stops: stopsResult.rows.map(row => ({ + stops: stopsResult.rows.map((row) => ({ id: row.id, order: row.stop_order, name: row.name, @@ -3271,111 +4516,153 @@ app.get('/schedules/:scheduleId/stops', async (req, res) => { }); } catch (err) { console.error(err); - res.status(500).json({ error: 'internal_error', details: err.message }); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // DRIVER - Mark stop as passed (updates realtime state) // ============================================ -app.post('/driver/schedules/:scheduleId/stops/:stopOrder/passed', authMiddleware, async (req, res) => { - try { - const scheduleId = Number(req.params.scheduleId); - const stopOrder = Number(req.params.stopOrder); +app.post( + "/driver/schedules/:scheduleId/stops/:stopOrder/passed", + authMiddleware, + async (req, res) => { + try { + const scheduleId = Number(req.params.scheduleId); + const stopOrder = Number(req.params.stopOrder); - if (!Number.isFinite(scheduleId) || !Number.isFinite(stopOrder) || stopOrder < 1) { - return res.status(400).json({ error: 'invalid_schedule_or_stop_order' }); - } - - const actualPassedAt = req.body?.actualPassedAt ? new Date(req.body.actualPassedAt) : new Date(); - if (Number.isNaN(actualPassedAt.getTime())) { - return res.status(400).json({ error: 'invalid_actual_passed_at' }); - } - - if (DEV_MOCK) { - const schedule = (_mock.schedules || []).find(s => Number(s.id) === scheduleId); - if (!schedule) { - return res.status(404).json({ error: 'schedule_not_found' }); + if ( + !Number.isFinite(scheduleId) || + !Number.isFinite(stopOrder) || + stopOrder < 1 + ) { + return res + .status(400) + .json({ error: "invalid_schedule_or_stop_order" }); } - const routeStop = (_mock.routeStops || []) - .find(rs => Number(rs.busLineId) === Number(schedule.busLineId) && Number(rs.stopOrder) === stopOrder); - - if (!routeStop) { - return res.status(404).json({ error: 'stop_not_found_for_schedule' }); + const actualPassedAt = req.body?.actualPassedAt + ? new Date(req.body.actualPassedAt) + : new Date(); + if (Number.isNaN(actualPassedAt.getTime())) { + return res.status(400).json({ error: "invalid_actual_passed_at" }); } - const stop = (_mock.stops || []).find(s => Number(s.id) === Number(routeStop.stopId)); - // Allow caller to specify which occurrence date this pertains to (for recurring templates) - const occurrenceDateKey = req.query.date || req.body.date || toDateKey(schedule.startTime); - const templateStart = new Date(schedule.startTime); - const scheduledPassedAt = new Date(`${occurrenceDateKey}T00:00:00`); - scheduledPassedAt.setHours(templateStart.getHours(), templateStart.getMinutes(), templateStart.getSeconds(), templateStart.getMilliseconds()); - scheduledPassedAt.setMinutes(scheduledPassedAt.getMinutes() + (Number(routeStop.estimatedArrivalMinutes) || 0)); - + if (DEV_MOCK) { + const schedule = (_mock.schedules || []).find( + (s) => Number(s.id) === scheduleId, + ); + if (!schedule) { + return res.status(404).json({ error: "schedule_not_found" }); + } - const existingIndex = (_mock.scheduleStopProgress || []).findIndex( - p => Number(p.scheduleId) === scheduleId && Number(p.stopOrder) === stopOrder - ); + const routeStop = (_mock.routeStops || []).find( + (rs) => + Number(rs.busLineId) === Number(schedule.busLineId) && + Number(rs.stopOrder) === stopOrder, + ); - const progressRow = { - id: existingIndex >= 0 ? _mock.scheduleStopProgress[existingIndex].id : _mock.nextScheduleStopProgressId++, - scheduleId, - busLineId: Number(schedule.busLineId), - stopId: Number(routeStop.stopId), - stopOrder, - stopName: stop?.name || routeStop.name || 'Unknown Stop', - estimatedArrivalMinutes: Number(routeStop.estimatedArrivalMinutes) || 0, - scheduledPassedAt: scheduledPassedAt.toISOString(), - scheduledPassedDate: occurrenceDateKey, - actualPassedAt: actualPassedAt.toISOString(), - delayMinutes, - markedByUserId: Number(req.user?.userId) || null, - createdAt: new Date().toISOString(), - }; + if (!routeStop) { + return res.status(404).json({ error: "stop_not_found_for_schedule" }); + } - if (existingIndex >= 0) { - _mock.scheduleStopProgress[existingIndex] = progressRow; - } else { - _mock.scheduleStopProgress.push(progressRow); + const stop = (_mock.stops || []).find( + (s) => Number(s.id) === Number(routeStop.stopId), + ); + // Allow caller to specify which occurrence date this pertains to (for recurring templates) + const occurrenceDateKey = + req.query.date || req.body.date || toDateKey(schedule.startTime); + const templateStart = new Date(schedule.startTime); + const scheduledPassedAt = new Date(`${occurrenceDateKey}T00:00:00`); + scheduledPassedAt.setHours( + templateStart.getHours(), + templateStart.getMinutes(), + templateStart.getSeconds(), + templateStart.getMilliseconds(), + ); + scheduledPassedAt.setMinutes( + scheduledPassedAt.getMinutes() + + (Number(routeStop.estimatedArrivalMinutes) || 0), + ); + + const existingIndex = (_mock.scheduleStopProgress || []).findIndex( + (p) => + Number(p.scheduleId) === scheduleId && + Number(p.stopOrder) === stopOrder, + ); + + const progressRow = { + id: + existingIndex >= 0 + ? _mock.scheduleStopProgress[existingIndex].id + : _mock.nextScheduleStopProgressId++, + scheduleId, + busLineId: Number(schedule.busLineId), + stopId: Number(routeStop.stopId), + stopOrder, + stopName: stop?.name || routeStop.name || "Unknown Stop", + estimatedArrivalMinutes: + Number(routeStop.estimatedArrivalMinutes) || 0, + scheduledPassedAt: scheduledPassedAt.toISOString(), + scheduledPassedDate: occurrenceDateKey, + actualPassedAt: actualPassedAt.toISOString(), + delayMinutes, + markedByUserId: Number(req.user?.userId) || null, + createdAt: new Date().toISOString(), + }; + + if (existingIndex >= 0) { + _mock.scheduleStopProgress[existingIndex] = progressRow; + } else { + _mock.scheduleStopProgress.push(progressRow); + } + + const totalStops = (_mock.routeStops || []).filter( + (rs) => Number(rs.busLineId) === Number(schedule.busLineId), + ).length; + const passedCount = (_mock.scheduleStopProgress || []).filter( + (p) => + Number(p.scheduleId) === scheduleId && + p.scheduledPassedDate === occurrenceDateKey, + ).length; + + if (passedCount >= totalStops) { + schedule.status = "completed"; + schedule.endTime = actualPassedAt.toISOString(); + } else if (schedule.status === "planned") { + schedule.status = "in_progress"; + } + + return res.json({ + scheduleId, + stopOrder, + status: schedule.status, + delayMinutes, + passedStops: passedCount, + totalStops, + occurrenceDate: occurrenceDateKey, + }); } - const totalStops = (_mock.routeStops || []).filter(rs => Number(rs.busLineId) === Number(schedule.busLineId)).length; - const passedCount = (_mock.scheduleStopProgress || []).filter(p => Number(p.scheduleId) === scheduleId && p.scheduledPassedDate === occurrenceDateKey).length; + await ensureScheduleStopProgressTable(); - if (passedCount >= totalStops) { - schedule.status = 'completed'; - schedule.endTime = actualPassedAt.toISOString(); - } else if (schedule.status === 'planned') { - schedule.status = 'in_progress'; - } - - return res.json({ - scheduleId, - stopOrder, - status: schedule.status, - delayMinutes, - passedStops: passedCount, - totalStops, - occurrenceDate: occurrenceDateKey, - }); - } - - await ensureScheduleStopProgressTable(); - - const scheduleResult = await db.query(` + const scheduleResult = await db.query( + ` SELECT s.id, s.bus_line_id, s.start_time, s.status FROM transit.schedules s WHERE s.id = $1 - `, [scheduleId]); + `, + [scheduleId], + ); - if (scheduleResult.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); - } + if (scheduleResult.rows.length === 0) { + return res.status(404).json({ error: "schedule_not_found" }); + } - const schedule = scheduleResult.rows[0]; + const schedule = scheduleResult.rows[0]; - const routeStopResult = await db.query(` + const routeStopResult = await db.query( + ` SELECT rs.stop_id, rs.stop_order, @@ -3386,82 +4673,125 @@ app.post('/driver/schedules/:scheduleId/stops/:stopOrder/passed', authMiddleware WHERE rs.bus_line_id = $1 AND rs.stop_order = $2 LIMIT 1 - `, [schedule.bus_line_id, stopOrder]); + `, + [schedule.bus_line_id, stopOrder], + ); - if (routeStopResult.rows.length === 0) { - return res.status(404).json({ error: 'stop_not_found_for_schedule' }); - } - - const routeStop = routeStopResult.rows[0]; - // Determine occurrence date (for recurring templates callers can pass ?date=YYYY-MM-DD or body.date) - let occurrenceDateKey = req.query.date || req.body.date || toDateKey(schedule.start_time); - - // If this schedule is a recurring template, map to or create a concrete instance for the occurrence date - const weekdays = parseWeekdaysValue(schedule.weekdays); - let instanceScheduleId = scheduleId; - if (weekdays && weekdays.length > 0) { - // If no date provided but template occurs today, use today - if (!occurrenceDateKey) { - const todayKey = new Date().toISOString().slice(0, 10); - if (scheduleOccursOnDate(schedule, todayKey)) occurrenceDateKey = todayKey; + if (routeStopResult.rows.length === 0) { + return res.status(404).json({ error: "stop_not_found_for_schedule" }); } - if (occurrenceDateKey) { - const existingInstance = await db.query( - `SELECT id FROM transit.schedules WHERE template_id = $1 AND DATE(start_time) = $2 LIMIT 1`, - [scheduleId, occurrenceDateKey] - ); + const routeStop = routeStopResult.rows[0]; + // Determine occurrence date (for recurring templates callers can pass ?date=YYYY-MM-DD or body.date) + let occurrenceDateKey = + req.query.date || req.body.date || toDateKey(schedule.start_time); - if (existingInstance.rows.length > 0) { - instanceScheduleId = existingInstance.rows[0].id; - } else { - // Create instance from template - const templateStart = new Date(schedule.start_time); - const templateEnd = schedule.end_time ? new Date(schedule.end_time) : null; - const occurrenceStart = new Date(`${occurrenceDateKey}T00:00:00`); - occurrenceStart.setHours(templateStart.getHours(), templateStart.getMinutes(), templateStart.getSeconds(), templateStart.getMilliseconds()); + // If this schedule is a recurring template, map to or create a concrete instance for the occurrence date + const weekdays = parseWeekdaysValue(schedule.weekdays); + let instanceScheduleId = scheduleId; + if (weekdays && weekdays.length > 0) { + // If no date provided but template occurs today, use today + if (!occurrenceDateKey) { + const todayKey = new Date().toISOString().slice(0, 10); + if (scheduleOccursOnDate(schedule, todayKey)) + occurrenceDateKey = todayKey; + } - let occurrenceEnd = null; - if (templateEnd && Number.isFinite(templateEnd.getTime()) && Number.isFinite(templateStart.getTime())) { - const duration = templateEnd.getTime() - templateStart.getTime(); - occurrenceEnd = new Date(occurrenceStart.getTime() + duration); - } + if (occurrenceDateKey) { + const existingInstance = await db.query( + `SELECT id FROM transit.schedules WHERE template_id = $1 AND DATE(start_time) = $2 LIMIT 1`, + [scheduleId, occurrenceDateKey], + ); - const hasTemplateIdCol = await db.query(` + if (existingInstance.rows.length > 0) { + instanceScheduleId = existingInstance.rows[0].id; + } else { + // Create instance from template + const templateStart = new Date(schedule.start_time); + const templateEnd = schedule.end_time + ? new Date(schedule.end_time) + : null; + const occurrenceStart = new Date(`${occurrenceDateKey}T00:00:00`); + occurrenceStart.setHours( + templateStart.getHours(), + templateStart.getMinutes(), + templateStart.getSeconds(), + templateStart.getMilliseconds(), + ); + + let occurrenceEnd = null; + if ( + templateEnd && + Number.isFinite(templateEnd.getTime()) && + Number.isFinite(templateStart.getTime()) + ) { + const duration = templateEnd.getTime() - templateStart.getTime(); + occurrenceEnd = new Date(occurrenceStart.getTime() + duration); + } + + const hasTemplateIdCol = await db.query(` SELECT 1 FROM information_schema.columns WHERE table_schema='transit' AND table_name='schedules' AND column_name='template_id' `); - let insertRes; - if (hasTemplateIdCol.rows.length > 0) { - insertRes = await db.query(` + let insertRes; + if (hasTemplateIdCol.rows.length > 0) { + insertRes = await db.query( + ` WITH next_id AS (SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules) INSERT INTO transit.schedules (id, bus_line_id, bus_id, driver_id, start_time, end_time, status, template_id) SELECT next_id.id, $1, $2, $3, $4, $5, 'planned', $6 FROM next_id RETURNING id - `, [schedule.bus_line_id, schedule.bus_id, schedule.driver_id, occurrenceStart.toISOString(), occurrenceEnd ? occurrenceEnd.toISOString() : null, scheduleId]); - } else { - insertRes = await db.query(` + `, + [ + schedule.bus_line_id, + schedule.bus_id, + schedule.driver_id, + occurrenceStart.toISOString(), + occurrenceEnd ? occurrenceEnd.toISOString() : null, + scheduleId, + ], + ); + } else { + insertRes = await db.query( + ` WITH next_id AS (SELECT COALESCE(MAX(id), 0) + 1 AS id FROM transit.schedules) INSERT INTO transit.schedules (id, bus_line_id, bus_id, driver_id, start_time, end_time, status) SELECT next_id.id, $1, $2, $3, $4, $5, 'planned' FROM next_id RETURNING id - `, [schedule.bus_line_id, schedule.bus_id, schedule.driver_id, occurrenceStart.toISOString(), occurrenceEnd ? occurrenceEnd.toISOString() : null]); - } + `, + [ + schedule.bus_line_id, + schedule.bus_id, + schedule.driver_id, + occurrenceStart.toISOString(), + occurrenceEnd ? occurrenceEnd.toISOString() : null, + ], + ); + } - instanceScheduleId = insertRes.rows[0].id; + instanceScheduleId = insertRes.rows[0].id; + } } } - } - const templateStart = new Date(schedule.start_time); - const scheduledPassedAt = new Date(`${occurrenceDateKey}T00:00:00`); - scheduledPassedAt.setHours(templateStart.getHours(), templateStart.getMinutes(), templateStart.getSeconds(), templateStart.getMilliseconds()); - scheduledPassedAt.setMinutes(scheduledPassedAt.getMinutes() + (Number(routeStop.estimated_arrival_minutes) || 0)); + const templateStart = new Date(schedule.start_time); + const scheduledPassedAt = new Date(`${occurrenceDateKey}T00:00:00`); + scheduledPassedAt.setHours( + templateStart.getHours(), + templateStart.getMinutes(), + templateStart.getSeconds(), + templateStart.getMilliseconds(), + ); + scheduledPassedAt.setMinutes( + scheduledPassedAt.getMinutes() + + (Number(routeStop.estimated_arrival_minutes) || 0), + ); - await db.query(` + await db.query( + ` INSERT INTO transit.schedule_stop_progress ( schedule_id, bus_line_id, @@ -3480,113 +4810,161 @@ app.post('/driver/schedules/:scheduleId/stops/:stopOrder/passed', authMiddleware actual_passed_at = EXCLUDED.actual_passed_at, marked_by_user_id = EXCLUDED.marked_by_user_id, created_at = NOW() - `, [ - instanceScheduleId, - Number(schedule.bus_line_id), - Number(routeStop.stop_id), - stopOrder, - routeStop.stop_name, - Number(routeStop.estimated_arrival_minutes) || 0, - scheduledPassedAt.toISOString(), - occurrenceDateKey, - actualPassedAt.toISOString(), - Number(req.user?.userId) || null, - ]); + `, + [ + instanceScheduleId, + Number(schedule.bus_line_id), + Number(routeStop.stop_id), + stopOrder, + routeStop.stop_name, + Number(routeStop.estimated_arrival_minutes) || 0, + scheduledPassedAt.toISOString(), + occurrenceDateKey, + actualPassedAt.toISOString(), + Number(req.user?.userId) || null, + ], + ); - const totalsResult = await db.query(` + const totalsResult = await db.query( + ` SELECT (SELECT COUNT(*)::int FROM transit.route_stops WHERE bus_line_id = $1) AS total_stops, (SELECT COUNT(*)::int FROM transit.schedule_stop_progress WHERE schedule_id = $2 AND scheduled_passed_date = $3) AS passed_stops - `, [schedule.bus_line_id, instanceScheduleId, occurrenceDateKey]); + `, + [schedule.bus_line_id, instanceScheduleId, occurrenceDateKey], + ); - const totalStops = Number(totalsResult.rows[0]?.total_stops || 0); - const passedStops = Number(totalsResult.rows[0]?.passed_stops || 0); - const isCompleted = totalStops > 0 && passedStops >= totalStops; - const nextStatus = isCompleted ? 'completed' : 'in_progress'; + const totalStops = Number(totalsResult.rows[0]?.total_stops || 0); + const passedStops = Number(totalsResult.rows[0]?.passed_stops || 0); + const isCompleted = totalStops > 0 && passedStops >= totalStops; + const nextStatus = isCompleted ? "completed" : "in_progress"; - await db.query(` + await db.query( + ` UPDATE transit.schedules SET status = $2, end_time = CASE WHEN $2 = 'completed' THEN $3 ELSE end_time END WHERE id = $1 - `, [instanceScheduleId, nextStatus, actualPassedAt.toISOString()]); + `, + [instanceScheduleId, nextStatus, actualPassedAt.toISOString()], + ); - // Calculate delay live - const delayMinutes = Math.round((actualPassedAt.getTime() - scheduledPassedAt.getTime()) / 60000); + // Calculate delay live + const delayMinutes = Math.round( + (actualPassedAt.getTime() - scheduledPassedAt.getTime()) / 60000, + ); - res.json({ - scheduleId, - stopOrder, - status: nextStatus, - delayMinutes, - passedStops, - totalStops, - }); - } catch (err) { - console.error('POST /driver/schedules/:scheduleId/stops/:stopOrder/passed error:', err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + res.json({ + scheduleId, + stopOrder, + status: nextStatus, + delayMinutes, + passedStops, + totalStops, + }); + } catch (err) { + console.error( + "POST /driver/schedules/:scheduleId/stops/:stopOrder/passed error:", + err, + ); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // PUBLIC - Live progress for passengers // ============================================ -app.get('/public/schedules/:scheduleId/progress', async (req, res) => { +app.get("/public/schedules/:scheduleId/progress", async (req, res) => { try { const scheduleId = Number(req.params.scheduleId); if (!Number.isFinite(scheduleId)) { - return res.status(400).json({ error: 'invalid_schedule_id' }); + return res.status(400).json({ error: "invalid_schedule_id" }); } if (DEV_MOCK) { - const schedule = (_mock.schedules || []).find(s => Number(s.id) === scheduleId); + const schedule = (_mock.schedules || []).find( + (s) => Number(s.id) === scheduleId, + ); if (!schedule) { - return res.status(404).json({ error: 'schedule_not_found' }); + return res.status(404).json({ error: "schedule_not_found" }); } - const busLine = (_mock.busLines || []).find(bl => Number(bl.id) === Number(schedule.busLineId)); + const busLine = (_mock.busLines || []).find( + (bl) => Number(bl.id) === Number(schedule.busLineId), + ); const routeStops = (_mock.routeStops || []) - .filter(rs => Number(rs.busLineId) === Number(schedule.busLineId)) + .filter((rs) => Number(rs.busLineId) === Number(schedule.busLineId)) .sort((a, b) => Number(a.stopOrder) - Number(b.stopOrder)); - const mockProgressDate = req.query.date || req.query.occurrenceDate || null; - let progressEvents = (_mock.scheduleStopProgress || []).filter(p => Number(p.scheduleId) === scheduleId); + const mockProgressDate = + req.query.date || req.query.occurrenceDate || null; + let progressEvents = (_mock.scheduleStopProgress || []).filter( + (p) => Number(p.scheduleId) === scheduleId, + ); if (mockProgressDate) { - progressEvents = progressEvents.filter(p => p.scheduledPassedDate === mockProgressDate); + progressEvents = progressEvents.filter( + (p) => p.scheduledPassedDate === mockProgressDate, + ); } - progressEvents = progressEvents.sort((a, b) => Number(a.stopOrder) - Number(b.stopOrder)); + progressEvents = progressEvents.sort( + (a, b) => Number(a.stopOrder) - Number(b.stopOrder), + ); - const progressByOrder = new Map(progressEvents.map(p => [Number(p.stopOrder), p])); - const latestProgress = progressEvents.length > 0 ? progressEvents[progressEvents.length - 1] : null; - const currentDelayMinutes = latestProgress ? Number(latestProgress.delayMinutes) || 0 : 0; + const progressByOrder = new Map( + progressEvents.map((p) => [Number(p.stopOrder), p]), + ); + const latestProgress = + progressEvents.length > 0 + ? progressEvents[progressEvents.length - 1] + : null; + const currentDelayMinutes = latestProgress + ? Number(latestProgress.delayMinutes) || 0 + : 0; - const occurrenceDateKey = req.query.date || req.query.occurrenceDate || toDateKey(schedule.startTime); - const stops = routeStops.map(rs => { - const stop = (_mock.stops || []).find(s => Number(s.id) === Number(rs.stopId)); + const occurrenceDateKey = + req.query.date || + req.query.occurrenceDate || + toDateKey(schedule.startTime); + const stops = routeStops.map((rs) => { + const stop = (_mock.stops || []).find( + (s) => Number(s.id) === Number(rs.stopId), + ); const templateStart = new Date(schedule.startTime); const scheduledTime = new Date(`${occurrenceDateKey}T00:00:00`); - scheduledTime.setHours(templateStart.getHours(), templateStart.getMinutes(), templateStart.getSeconds(), templateStart.getMilliseconds()); - scheduledTime.setMinutes(scheduledTime.getMinutes() + (Number(rs.estimatedArrivalMinutes) || 0)); + scheduledTime.setHours( + templateStart.getHours(), + templateStart.getMinutes(), + templateStart.getSeconds(), + templateStart.getMilliseconds(), + ); + scheduledTime.setMinutes( + scheduledTime.getMinutes() + + (Number(rs.estimatedArrivalMinutes) || 0), + ); const progress = progressByOrder.get(Number(rs.stopOrder)); return { stopId: Number(rs.stopId), stopOrder: Number(rs.stopOrder), - stopName: stop?.name || rs.name || 'Unknown Stop', + stopName: stop?.name || rs.name || "Unknown Stop", estimatedArrivalMinutes: Number(rs.estimatedArrivalMinutes) || 0, scheduledPassedAt: scheduledTime.toISOString(), actualPassedAt: progress?.actualPassedAt || null, delayMinutes: progress ? Number(progress.delayMinutes) || 0 : null, - status: progress ? 'passed' : 'pending', + status: progress ? "passed" : "pending", }; }); - const passedStops = stops.filter(s => s.status === 'passed'); - const nextStop = stops.find(s => s.status === 'pending') || null; - const driverState = passedStops.length === 0 - ? 'not_started' - : (passedStops.length >= stops.length ? 'completed' : 'en_route'); + const passedStops = stops.filter((s) => s.status === "passed"); + const nextStop = stops.find((s) => s.status === "pending") || null; + const driverState = + passedStops.length === 0 + ? "not_started" + : passedStops.length >= stops.length + ? "completed" + : "en_route"; return res.json({ schedule: { @@ -3605,7 +4983,10 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { nextStop: nextStop ? { ...nextStop, - predictedPassedAt: new Date(new Date(nextStop.scheduledPassedAt).getTime() + currentDelayMinutes * 60000).toISOString(), + predictedPassedAt: new Date( + new Date(nextStop.scheduledPassedAt).getTime() + + currentDelayMinutes * 60000, + ).toISOString(), } : null, stops, @@ -3615,7 +4996,8 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { await ensureScheduleStopProgressTable(); - const scheduleResult = await db.query(` + const scheduleResult = await db.query( + ` SELECT s.id, s.bus_line_id, @@ -3629,15 +5011,18 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { JOIN transit.bus_lines bl ON bl.id = s.bus_line_id WHERE s.id = $1 LIMIT 1 - `, [scheduleId]); + `, + [scheduleId], + ); if (scheduleResult.rows.length === 0) { - return res.status(404).json({ error: 'schedule_not_found' }); + return res.status(404).json({ error: "schedule_not_found" }); } const schedule = scheduleResult.rows[0]; - const routeStopsResult = await db.query(` + const routeStopsResult = await db.query( + ` SELECT rs.stop_id, rs.stop_order, @@ -3647,13 +5032,16 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { JOIN transit.stops s ON s.id = rs.stop_id WHERE rs.bus_line_id = $1 ORDER BY rs.stop_order ASC - `, [schedule.bus_line_id]); + `, + [schedule.bus_line_id], + ); // Allow querying progress for a specific occurrence date (recurring templates) const progressDate = req.query.date || req.query.occurrenceDate || null; let progressResult; if (progressDate) { - progressResult = await db.query(` + progressResult = await db.query( + ` SELECT stop_order, actual_passed_at, @@ -3662,9 +5050,12 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { FROM transit.schedule_stop_progress WHERE schedule_id = $1 AND scheduled_passed_date = $2 ORDER BY stop_order ASC - `, [scheduleId, progressDate]); + `, + [scheduleId, progressDate], + ); } else { - progressResult = await db.query(` + progressResult = await db.query( + ` SELECT stop_order, actual_passed_at, @@ -3673,31 +5064,48 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { FROM transit.schedule_stop_progress WHERE schedule_id = $1 ORDER BY scheduled_passed_at ASC, stop_order ASC - `, [scheduleId]); + `, + [scheduleId], + ); } const progressByOrder = new Map( - progressResult.rows.map(row => [Number(row.stop_order), row]) + progressResult.rows.map((row) => [Number(row.stop_order), row]), ); - const latestProgress = progressResult.rows.length > 0 - ? progressResult.rows[progressResult.rows.length - 1] - : null; + const latestProgress = + progressResult.rows.length > 0 + ? progressResult.rows[progressResult.rows.length - 1] + : null; // Calculate delay live from latest passed stop let currentDelayMinutes = null; - if (latestProgress && latestProgress.actual_passed_at && latestProgress.scheduled_passed_at) { + if ( + latestProgress && + latestProgress.actual_passed_at && + latestProgress.scheduled_passed_at + ) { const actualTime = new Date(latestProgress.actual_passed_at); const scheduledTime = new Date(latestProgress.scheduled_passed_at); - currentDelayMinutes = Math.round((actualTime.getTime() - scheduledTime.getTime()) / 60000); + currentDelayMinutes = Math.round( + (actualTime.getTime() - scheduledTime.getTime()) / 60000, + ); } - const occurrenceDateKeyForMapping = progressDate || toDateKey(schedule.start_time); + const occurrenceDateKeyForMapping = + progressDate || toDateKey(schedule.start_time); - const stops = routeStopsResult.rows.map(row => { + const stops = routeStopsResult.rows.map((row) => { const etaMinutes = Number(row.estimated_arrival_minutes) || 0; const templateStart = new Date(schedule.start_time); - const scheduledPassedAt = new Date(`${occurrenceDateKeyForMapping}T00:00:00`); - scheduledPassedAt.setHours(templateStart.getHours(), templateStart.getMinutes(), templateStart.getSeconds(), templateStart.getMilliseconds()); + const scheduledPassedAt = new Date( + `${occurrenceDateKeyForMapping}T00:00:00`, + ); + scheduledPassedAt.setHours( + templateStart.getHours(), + templateStart.getMinutes(), + templateStart.getSeconds(), + templateStart.getMilliseconds(), + ); scheduledPassedAt.setMinutes(scheduledPassedAt.getMinutes() + etaMinutes); const progress = progressByOrder.get(Number(row.stop_order)); @@ -3713,20 +5121,25 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { if (progress?.actual_passed_at && progress?.scheduled_passed_at) { const actualTime = new Date(progress.actual_passed_at); const scheduledTime = new Date(progress.scheduled_passed_at); - return Math.round((actualTime.getTime() - scheduledTime.getTime()) / 60000); + return Math.round( + (actualTime.getTime() - scheduledTime.getTime()) / 60000, + ); } return null; })(), - status: progress ? 'passed' : 'pending', + status: progress ? "passed" : "pending", }; }); - const passedStops = stops.filter(s => s.status === 'passed'); - const nextStop = stops.find(s => s.status === 'pending') || null; + const passedStops = stops.filter((s) => s.status === "passed"); + const nextStop = stops.find((s) => s.status === "pending") || null; - const driverState = passedStops.length === 0 - ? 'not_started' - : (passedStops.length >= stops.length ? 'completed' : 'en_route'); + const driverState = + passedStops.length === 0 + ? "not_started" + : passedStops.length >= stops.length + ? "completed" + : "en_route"; res.json({ schedule: { @@ -3745,130 +5158,157 @@ app.get('/public/schedules/:scheduleId/progress', async (req, res) => { nextStop: nextStop ? { ...nextStop, - predictedPassedAt: new Date(new Date(nextStop.scheduledPassedAt).getTime() + currentDelayMinutes * 60000).toISOString(), + predictedPassedAt: new Date( + new Date(nextStop.scheduledPassedAt).getTime() + + currentDelayMinutes * 60000, + ).toISOString(), } : null, stops, updatedAt: latestProgress?.actual_passed_at || null, }); } catch (err) { - console.error('GET /public/schedules/:scheduleId/progress error:', err); - res.status(500).json({ error: 'internal_error', details: err.message }); + console.error("GET /public/schedules/:scheduleId/progress error:", err); + res.status(500).json({ error: "internal_error", details: err.message }); } }); // ============================================ // ADMIN - Check driver conflicts for generated schedules // ============================================ -app.post('/admin/check-driver-conflicts', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { generatedTimes, drivers, busLineId, busId, weekdays = [] } = req.body; +app.post( + "/admin/check-driver-conflicts", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { + generatedTimes, + drivers, + busLineId, + busId, + weekdays = [], + } = req.body; - if (!Array.isArray(generatedTimes) || !Array.isArray(drivers)) { - return res.status(400).json({ error: 'missing_required_fields' }); - } + if (!Array.isArray(generatedTimes) || !Array.isArray(drivers)) { + return res.status(400).json({ error: "missing_required_fields" }); + } - // Get all existing schedules - const schedResult = DEV_MOCK - ? { rows: _mock.schedules || [] } - : await db.query(`SELECT * FROM transit.schedules WHERE status IN ('planned', 'active')`); + // Get all existing schedules + const schedResult = DEV_MOCK + ? { rows: _mock.schedules || [] } + : await db.query( + `SELECT * FROM transit.schedules WHERE status IN ('planned', 'active')`, + ); - const existingSchedules = schedResult.rows.map(row => ({ - ...row, - weekdays: parseWeekdaysValue(row.weekdays || row.weekday_list || '[]'), - })); + const existingSchedules = schedResult.rows.map((row) => ({ + ...row, + weekdays: parseWeekdaysValue(row.weekdays || row.weekday_list || "[]"), + })); - // For each generated time, check conflicts with each driver - const assignments = generatedTimes.map(timeStr => { - const driverAssignments = drivers.map(driver => { - const hasConflict = checkDriverConflict( - driver.id, - timeStr, - timeStr, // Will be checked with start/end times of conflicts - weekdays, - existingSchedules - ); + // For each generated time, check conflicts with each driver + const assignments = generatedTimes.map((timeStr) => { + const driverAssignments = drivers.map((driver) => { + const hasConflict = checkDriverConflict( + driver.id, + timeStr, + timeStr, // Will be checked with start/end times of conflicts + weekdays, + existingSchedules, + ); + + return { + driverId: driver.id, + driverName: driver.name, + hasConflict, + assigned: false, + }; + }); return { - driverId: driver.id, - driverName: driver.name, - hasConflict, - assigned: false, + time: timeStr, + driverAssignments, + assignedDriverId: null, }; }); - return { - time: timeStr, - driverAssignments, - assignedDriverId: null, - }; - }); - - res.json({ assignments }); - } catch (err) { - console.error('/admin/check-driver-conflicts error:', err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + res.json({ assignments }); + } catch (err) { + console.error("/admin/check-driver-conflicts error:", err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); // ============================================ // ADMIN - Auto-assign schedules to drivers // ============================================ -app.post('/admin/assign-schedules', authMiddleware, adminMiddleware, async (req, res) => { - try { - const { assignments, busLineId, busId, weekdays = [] } = req.body; +app.post( + "/admin/assign-schedules", + authMiddleware, + adminMiddleware, + async (req, res) => { + try { + const { assignments, busLineId, busId, weekdays = [] } = req.body; - if (!Array.isArray(assignments)) { - return res.status(400).json({ error: 'missing_assignments' }); - } - - // Group assignments by driver (round-robin or custom) - const schedulesToCreate = []; - let driverIndex = 0; - - for (const assignment of assignments) { - if (!assignment.time || !assignment.assignedDriverId) continue; - - const startTime = assignment.time; - const endTime = assignment.endTime; - - schedulesToCreate.push({ - busLineId, - busId, - driverId: assignment.assignedDriverId, - startTime, - endTime, - weekdays, - }); - } - - // Use bulk create endpoint logic - if (DEV_MOCK) { - if (!_mock.schedules) _mock.schedules = []; - const createdSchedules = []; - - for (const entry of schedulesToCreate) { - const schedule = { - id: (_mock.nextScheduleId || 1), - ...entry, - status: 'planned', - createdAt: new Date().toISOString(), - }; - _mock.nextScheduleId = ((_mock.nextScheduleId || 1) + 1); - _mock.schedules.push(schedule); - createdSchedules.push(schedule); + if (!Array.isArray(assignments)) { + return res.status(400).json({ error: "missing_assignments" }); } - return res.status(201).json({ createdCount: createdSchedules.length, schedules: createdSchedules }); - } + // Group assignments by driver (round-robin or custom) + const schedulesToCreate = []; + let driverIndex = 0; - // For DB: use bulk insert - res.status(201).json({ createdCount: schedulesToCreate.length, message: 'would_insert_schedules' }); - } catch (err) { - console.error('/admin/assign-schedules error:', err); - res.status(500).json({ error: 'internal_error', details: err.message }); - } -}); + for (const assignment of assignments) { + if (!assignment.time || !assignment.assignedDriverId) continue; + + const startTime = assignment.time; + const endTime = assignment.endTime; + + schedulesToCreate.push({ + busLineId, + busId, + driverId: assignment.assignedDriverId, + startTime, + endTime, + weekdays, + }); + } + + // Use bulk create endpoint logic + if (DEV_MOCK) { + if (!_mock.schedules) _mock.schedules = []; + const createdSchedules = []; + + for (const entry of schedulesToCreate) { + const schedule = { + id: _mock.nextScheduleId || 1, + ...entry, + status: "planned", + createdAt: new Date().toISOString(), + }; + _mock.nextScheduleId = (_mock.nextScheduleId || 1) + 1; + _mock.schedules.push(schedule); + createdSchedules.push(schedule); + } + + return res.status(201).json({ + createdCount: createdSchedules.length, + schedules: createdSchedules, + }); + } + + // For DB: use bulk insert + res.status(201).json({ + createdCount: schedulesToCreate.length, + message: "would_insert_schedules", + }); + } catch (err) { + console.error("/admin/assign-schedules error:", err); + res.status(500).json({ error: "internal_error", details: err.message }); + } + }, +); const PORT = process.env.PORT || 5001; @@ -3876,11 +5316,11 @@ const PORT = process.env.PORT || 5001; (async () => { try { await ensureScheduleStopProgressTable(); - console.log('✓ Database tables initialized'); + console.log("✓ Database tables initialized"); } catch (err) { - console.error('⚠ Error initializing tables:', err.message); + console.error("⚠ Error initializing tables:", err.message); } - + app.listen(PORT, () => console.log(`Transit API listening on ${PORT}`)); })(); diff --git a/windows_xp_driver_ui/Makefile b/windows_xp_driver_ui/Makefile new file mode 100644 index 0000000..e106a7c --- /dev/null +++ b/windows_xp_driver_ui/Makefile @@ -0,0 +1,12 @@ +CC = i686-w64-mingw32-gcc +CFLAGS = -O2 -Wall -D_WIN32_WINNT=0x0501 -mwindows +LDFLAGS = -lwinhttp +TARGET = driver_xp.exe + +all: $(TARGET) + +$(TARGET): driver_xp.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +clean: + rm -f $(TARGET) diff --git a/windows_xp_driver_ui/README.md b/windows_xp_driver_ui/README.md new file mode 100644 index 0000000..972e61e --- /dev/null +++ b/windows_xp_driver_ui/README.md @@ -0,0 +1,27 @@ +# Transit Driver XP + +Native Win32 driver client targeting Windows XP-era systems. + +The client authenticates with `auth_api` at `192.168.68.66:5000`, then requests +assigned weekly schedule templates from `transit_api` at +`192.168.68.66:5001/driver/schedules/daily`. + +Schedules are assigned through `transit.shifts`; the client does not expect a +`driver_id`, `bus_id`, or `weekdays` column on `transit.schedules`. + +## Build + +```bash +make clean && make +``` + +## Distribution + +Build the installer with: + +```bash +makensis installer.nsi +``` + +Distribute `TransitDriverXPSetup.exe`. It detects an existing installation, +stops the previous client, and replaces it in the same directory. diff --git a/windows_xp_driver_ui/TransitDriverXPSetup.exe b/windows_xp_driver_ui/TransitDriverXPSetup.exe new file mode 100644 index 0000000000000000000000000000000000000000..d3b05d41662ef6d43f06c03e702171d3006e14b1 GIT binary patch literal 258619 zcmeFadwf*ooj-minZN{sXM_w!4G=Ucsi;JuHDc;WGJr}j7ZTLMO0CAaUKi#Fu22(C zrgAurrIxg|E4!p&cj+#z?1~T-bV6tX*4l6>pjZibJL92>mxcf?^L@WR&zU4>`+fcX z`u_e|UQFhk=kj?z&*%1hp3ifcirZFdnVP2g@kymLtqtG&7nZ;O_dgBDK5N`dXK5R= ze?Pv>H|zJ~=Y0R3MFkHneDLmt-@U)!d*6NFfd``ncidI5(0HKWo(BrbZ>}l0|G_)& zx_rclVTB&(%7~`T@(tB~_4?v_(yjGqdggFn#%0>mNSWr-w6z)9uyQ;H@!7yqVfCD$ z@E(E`;xGRac=KK|v~Z)Q6{xbT%=-A}eW{m~er>}jJU%o;YXC0)ul`*gy=zGnjWw;r zA0ouQ_ICo$_*bB53ogI&yV39B*r_b7Nm8_-D0MZpbcmJbu&10I424<$&L! zh2KM#&=|DVrsJjaYvlm)fB*fzq(H6p{@l8n#-r=es2vH|wK;Z0(26wJkzDhn9zEYY zIbDx^3O?Cm*1un-nKSc()-C?zA*943>+I6X>}i@+Ug)&8n4^{a9DJ`Bo&COocSm=2($H{H`y) z^LMw;PhFIUieUX<#$GFoMTz1B%WMm3E0tVT+qD6E%J(57_iW1pHulmXf#Y2?5z!gb zig!*8-G{72*WU9MGvofNtjIAt99WkT&Ai^daGgIoG`2H3_69d^UC_uidrn&&R-f79 z%kHoa*z=EBWr1n#mRqDnzor^Ak4~f%hos2^gzDR4-xTO=&xnpNm*r^Dq2}RN!q!cJ z;+@64R>gZ$vjl^C?&*`V`|X=q*2oOI=UcTs{VeboI?sZD;K0pLk)Game{^iDH#*s_ z30S+#*TM~lA2WCQ%w_LsdigG^Cg7fD{lWY&Y^HqXJE?}lzG&ujcU3C#p8KQXgh$A2 z0T1PByCPto7`J%Z%jCAA)}D8ZcUtq0O&uZFO}J;5VE36lEwFBgG1}^|mmRzCAN}SI zpZV_TSV}MNu%-pBcf0#(f!b7S$YBd9VNVe?jT~zsJS*auTb^2l#_wVT`=`b>_iGxg#TWMcFL!Pe1_NTQ0%!XlrvSo=fHf;v6MDqw()%>6 zO5Z$+l{yBH=<*j{fQ~l)t^k9rb31uyZEGQ7cGw?FEE!>sDD1gji#G*8kA`@~7evXL zRhR?S3J~rce-B;SW*!X|Ct|ye%rnrMTNjV*{hw3{W^^88+g=uAw~ZX?UgL*!ub@{} zpA{_x%d(Lga7Fi0&18PQ1bZg8*p0N??Ke0=io$;BraPp9U@o8M9S z0pREdeyPy+VF}Em1*Mq)XWVN?g5Z-C+m2%Hc{J(l59m4zjo*OoLo{Lu<2D7TH~~q| z$wEyO3y5Y5LIU_s09M5WVdG+RE1`NgM_U!{QgjZAwz=qUl{@n*$(7&${Y>ZH2f!>e zeKyG<_GX^gFn4tEZWyBgFvv+~b)&_!y5vw@pxd#;BcsFjB(%O%Dl=>2jxfYIFh4mR z^qjAR#5|gZVpQFNq)3-j{~ZWYrO42WLxBTzBffPhIsuNoPL3IY7LIP8|Em>SfMk1X z6aO|h@b3#O2=48H%&HdK-yNPgMw*V@iM)7>}>6fH!M`OufBhB2p zoLD?oWc!;7ApFsxtzY8H9cuZT7vMomgA)T(MG3b61({)PeUcSXjMa4O@EA>NVO1|B z*^LA{PUywEZGW7lfYYjN7sgo8QYrQ-p>NWHV%q+#ER_85xq#$=La{_tm!XNGeL#kn zmSd8xH!UP|sf+$lu9N>X0>h=h@EB<3n5R;yxpp%d=w+uuOGt**V6<1LIOWVku^Nmg zQ0=liG_8(f@Z+Ez#G3Ft}-#jP%jGXNRvft~(+O0@vJ5B&qcYCue0IbOQ?7t~BHq+sl{atfXM zPqMqHwGgwcvppw-5%JhFzEq_FG~#Wp zc`D-v*QZlv-HmF+yFttI;?2}qGr#HQ$Ay;SQB^H%g{otjepQ}vC?=9r>oqLDs-N^%)XQ!dktB!gVFQ6 z&K@Dveo>1Or!uOllAH2iE2tK%{3(nN`+M?EFLCZ%N|5JYIPW4EGmqFRbcMw^FtB)~ z=#YZK;sgWoZ1jqg_U(O{Eb7&AhR-KK%|5^0lnZ>!zAR&SDr*9b*DJ6~=|)i;nv-(e zIh6Wj0bbaP;)BTeh#9+)QB{@7nnSZW&+{|ji`y}pO(O9*m8ZiNlX+omC7EzEm>R(f z;b=iz(lKOS%fTfF%#m?B6>3m=g7j2qBREwVJE&XNfmY_J)5Zkimqh#Q4rS+NFd8$4 zLDKZW_aIHVEcfxCa+jbSv=^yCtw1P_+Q}K2(BgwoJ+}q`MQZ)MFehhNXXgNh->NFw zgFl?FX(iG@z3KN*!V>A09$Rw`EjpQ#d^BG)L`-=bjM$l`+?fRTF8LseqdjV|vkGPE zNUjOMJ9*DgPq>^nkZyIg0YS}b=4k6|L5f$)>U8Geje4O>y7LjnfGRuAg7saf6x*qn z_rbGn%0gSQUb~rg`jLRIQ(yl1XdvE+PU|-x^Y!{Zqh2_Vvwl@9^~eYsqG$0|?dw`{ zRwJAgP1Aq$D(biy#enU96liY1D>=Xp?D>Lbfc97Dw;iu_=2o&>L=@nRKq|BrTy=-Q zQ@}SZupWz}a`G>Lo~FbK)SL^;?O8Ezn84`C5l6he9O;@^KlcL9^ojKhaq@^cq=H|E z!7#el{jk2vJPmrHnRm{kgJHiw4mnY@V|6I!;QM%v^%?~w9m|H>VknX?p{)MYJRoK_ zvlq!HznLF zkL^ zil)aUhwjuNaj~em_LKrlu|U8jGciFNW`7gT!e2n5BJr{P4s?pry8nALBWZnN%75Pi zLhpiCVs@r2JKJ$4ETHLXAyhNrrzrxnoRN~s`c&g|Zt`^qRxB009DQ!O13Dj{P8VBD zT@Qv1n7|>egd|>P7_yXHI}9BK7(XU`=x`Y9PKB;T&RnZwt}UJ|tH;Y|`<()y?IB}j zT4M+*`7Rs<(KF|^&xE6Xz;I^sxvN(!t}m(s^i@oL|5K`Kr%KO|^m3;E9_dvu`{I?4 z*JBMxRAQ1vK;jZ82K)sj?KmZ|{X|Ce+iDEP;7b#j2YWb6&6PkqZz4m@1@4HLfK%qt ze8|o!N`}?tE|Pj*FS!9F-JAOPj~1&H3P{2TtvO?fE}ai$7>tbKM7?>9hVR||)>s8r zQlXjBY|s*?r5NrVM-@$9Zqeway&kg3nTN5ZvNHJ)GT_eVpbf!$DFDEoibBKa)h2IY z#bydyvI38aEbqW#LYu@U?&dROM{3`K+8BYpbrykUmM#a&od040_$y?*t0XcNdL0Fm zMIp87&%^5?x(jEMrmw*(vbw6>q55X=fIW{e9q(fBuoxjK)z(GF zPQ&roN(h=BXq5GaY4arL<|rFd;p|f0h0GemRIbfSufs zL&kHcky3L=M6ug`Oy=l+zzoyHLLg6ZAOn;^tAUx!CUz`x3v?7_{rG{_HFP1s?m;lu z0%m2U(#q`^$2+q&qmZw41OD1F=z>*8e9J={S$CV+3X&USI}#3hr#d zqjRyC54^FpREUww>g18(BB^(vh4hF}hv)SCP+1=MM>2y;w&}DjlYv_D*LYO1o4tRg za&f*P!{2Z53Oj(;3n&wt@QPU)UPFBScf2x?a1=7@oDI2zG;oZuV$b5Q*MH%=Cy}v@ zNUt})P0j4JUZ9v*Th~e2RHhvi*UwtL9!bh+j%~oBCwee=zpsXW)IVbEgQ@G4w!(Uo z3yrd4-2{n(zl0B6r(ToGVN+G_dVwh>R~{j$YR;QC(o>+i`v4oA<5;!{RwX^7yqMr> z#3ffV!6kXD?+GXJrrb(j43nN=s49aT#FOD>x_imn0170+_@z0ErFm3Y3KtABSs0OV zb~yJ;a35gcfm%(^OO68G;vyu^S3u|iI#&Wnm5Pf1E;tT8V1^uTj-u;z^nR4z1f5BB zQ)m^Y0!SuVbff+V6{qL|LKqO#5My!MCn@Fpb}l(KI3KwT?hWEe3(C~g3=FG#q3mX<23>)r9}R1P&OlyM&U^6w>qNlC zRR0mL)oFLH)3^u|1mK0SSWWZk#LrTSPC+q3$#!NjITy)R2U+?%vGowBed1IgWu7ge z9e4s4s&NZ7oWtNF^o0g0)jc3N?sR*E%%ZShplt}#8ORYA0g*zX8iw;h40g|9Yv|2! z_9h1=X|B=_PwDm8f6>kXo1^0)rWEcqDj^keD4#Du^-&$ugp@lJ4<8avgff=gpt6Kc zTQEzh>zBGiEc7cR(ZThE`xkf}kd)uE#1lw@q_B%96`}A%kyK-gKkbPSyr;315HgT+ z1WEx*cq=TWdDNuQhm_N?r84=bPhC|^6N5YWZEy9spL?RgnA-c(H@JGo`JpK%t zw}Xtdl78VU00{|J!m)NU&} z6m&|(p@1g)nW`=at{#RmNDl%vG2MpJwLv-gm6&QS0dUp-H033Iq?}8jBvr{r5wucPbq!=jg{;JJ_%U8p$m&A!@^QSv3Q+`O zb;9T9V_jmjX%o|f1d(KsPcaX#uJ0-@Lna;T!GYoW7aED_K{7YPQ7qyvZ~MyMQwsh$>w^h|xEB z3{-k-1(;;7ru;%rnwH|tJnb{ap+ymY6X5A1F_z_wszz|F+Lv%|Fi-o9(aIrw9wl0t z-Nu=kdwFrfJe_G=q85QP%o>S6I-=$|Qt%X~_hh zq_(pU{I9BVbv0=82SNHpF4+fU(*5`a5Jx|}`7d7jJMk_`c?~GtqGCh~K(rh_!Hb!v zW~c&~i)=`rlH_X_tC^PWRVa?-LR$`c^@NXpOF62PnTcfWKmn z#4&scF-V{d7dqwq9b&17(s2$+nLcB=#BpvUqjMO7ZMu#i*kpcT@?rGIGl?>oz|k0^ zCY;{|xcXK`RVhnXlj&VE+*|QzOhuD2#JY#UVoDan;`o-;Ib@2Qz5CcdbCXpyN|M;g@RkN*F6N`Vx6q(rxY6XZIB!bh;{l znAKU|C5|ilBGWtCH5Jh)QQo5-`wU4K#KfKjJ-Y*mkoJnYLFquhcj`}t7KrJDqk$QM zI1Qrpcw2`z(^foMQt@y?XY>H#Q6K*QDX#?{G%#}80mQlX^+D42X0@|OKp^x}SO z0wjyb6o%VtZV;xx7yCwX4?!oa=36M0lsV-868W(N_FYKoBQGU4pitV?+$>l{&t+lH z06|Db&ypz0B-rJ8Qz8T-qQktWVaBZb5)})`sW9 z?5)?;`06eHt?ng*UdyXaV_h(sTe3eo5?tW?#oCiR&9Aqt{R+DnFap~OBN(wqpCubr=mDl!Dfr< zzYW8Zd==jI0D;a!0mhT+J!`5KQB$G!P*h^S6>-@&0AQr7^KWdw3PI8R)Ld`4&VMJp z{xugCM1>@J0Dx;FDVkt$H@na+v53jN(3dm$^Z?9EU7h-ZGOR(Pr|@E@kXfsTR=%a$z))+8;|GNRLS%3+YY!kcLoSZlN96$TOmJ#-KqEdF0x$CKPAqPoX-tIUNl)z*oP<4#X?D$s=IN z+?KSqXZjVgzBjqFYl2t`uk`dcC+;8Sw#zJ$FH4k7aTxT0GuuNqJCjq#ANQJ_xu% zGk2^P1OgS~WqCy45tx9va~p38XlPGy+M{`{nKm9xmy+I)oa#4$?P_2TY@Y#Cvu~XK z#6_s+l~fxUqLZ){Eg!&P?aSYyQ%7<_-I`eaPuL`ZzF%ADG$OA$`E68>?X*MOb`=j7 zMs+(b?49+vKlwa2RFpqXUJUfo>4cIcf66QwnlN?GxfymIa+Z7?sgNTuKyxmmE=oY` z0>1tftzo(peIWjVOlY9Wun(!wc5sujwxqUyI8ky2uF4h+&!OG}R-)7u-n8)yjGmjJ z#CVC4%Ty>qSxW9kyMwe_luTSZF<-^K&K%%mb%<~2%;dA6rf=$Qck-}2ob@tydiXlL z!4KXCh)QBQFI`W_><8WJGjN#(Nlh-nz>PT(yy6Q(S}(RSqApFqaKWfQp6Z2T({b0P zGFGFGdFuiF1(zTaiI$QVpn$KGOSr|mvD?V%{>qNRd~6T0m*v1SUH&why0@_!Gvk_}}?NV%?$fTT<46qGMp~3_J7%HnU`A z_2F+T|D^je$q;ZVyj#4RgrK7fidjXlYX^<7D^z( zRre96OHsDEn){$+e;&8}t_1paBnSJ|N`sJDqp)~4t?Nnl3JtSSBy(Izg?0$Ng6`1L zQ7n2-OVj&h;>q2K67d(fcZG=Bp$4Rvbn7$J9waYNo$kY|_OM!h;=)<~WOOfH5Zk~^ zf=v!#KcQQ9mx@u3-_^&BrQBDFJz(b12`y`Gz$PkgOFRb_K?U3M386UA$}IOzB#!_k z(CH^4*OG9m)j65IfoX!!Q9Ait9vJqMWb*NXN#OR`1nC9MI?yg&EVH8)Jt z$fMWMtd0Gz%YtB4K#wd7>XCg`^Bm9{x~peqJ%c{6#b%^b#bbBki|B%gl3a&(6=)p~ z`)aRdJ~svMO_2pr$E@R%{J$E$PJ4Ia%+#jDbUK4fr9-6iJP) ztOQVDIcv&xJYhYDP)m203x}9sCha=f_YGi(omvzE0X#?Pc!2Ow<&Ud zjT6N%yX1uN+{<`MxmUi7Q77eIDrjQgio3PsbN%LbWN40X_;*q$K(R80y04RuDca5h z-u`dsDfS;1A2g57QryUL-~hH#`_j8@WFi=E(+;2y@SH#dXdJzeqq1Qg#l^VbtAPV+ z^|dJ1iOg0iSliPevw0e>*fWq~wTc4>@n?8|iKgSzFr8LDeOAKhAvhzYP@+eb_ybB5 zC$M0FEcgMbkcP&w2(@l>j@gUVWR zNS}U4hAlb zU4LQ~vKyCmfAY$&-_c`F$jjR|Jg_Kg{Gf5!fkhA2M-8KK+1}a4efJsnWnIl#&G^R73rgvkrOibf!jn^w?j3jx|Px#mv@OpiOV&bzgKyDytmN zZ9~zDZ(fJrbbO1BtB4rcXm*I}+%GELQ@Gd?|7Q(WhtipK9gG4WR(y-rY#X?S$(v1#7m$V#o zsk`dsHQU2l!&D9715kg4#|9OBiqFgRRl7$}7-gZB$m9mMVSM>df@)n0Nk$3>Oy}oF z;e0xTgt15faJ&s|7(qlDQlaITCKYo5fy;I~l#yxW(qKF`8GXXoAlpSZ!jM700-hwD z2lP^*w^1C29;hh!NI(k-wCs$6F|4agg^mKWT`CgwBW;b;m%R&d4J2{?Je8akH%P4) zR>yyqfl{sXcJ$3pum<8E7jS0@E3_480O3veo|-Hxnv|t4fkYkRzOgT1%(#T4H$9J% z`sQH0d9uLBG7}l@XL!6oJ!ZW`I4F08@nF5VB%>yNONKGB{&?7bC}j-m63axTYFsj; zS}W6xa~hXq%;v*cC^PI(>P4Caz5G)omusb(F`;qE=>qKv%@|kT6+R7RObnqS#-GsE z0bQtm7W#>Z?FH5WxNrV1nxf<_q=Z;&;g{6a*&;I~I~LyE zxWs=Q=pM~7oebA$yt+WUT{Aw9-x843e0JzA5S&`Bt7bl6Gh9IPSVAKOCOiNmg%m{W zuqx)I-G(%*ePyDs^J|Xi`GxV#+5|krjsbn=4&=k9BZ8)n>ePg)p#TC5nGke_qaWs0qOUxT=uqbuS72c!%lRB%pi^`XZD5F7|FQ9D zSe5?xr@&j%7@R^0Y?b%K2yjJaW?WC8#1GxjV+m#px-5{a1&c-4xbwdlp(!9t#=IV8 zuR#U4Ea-$SW)RqAi=#Gj>CfQxHl&h$k-t=eC3CB@UG2WaD zDC8;ROlEn4cx9R#(L3X@tBDOtJOHl8w~AQ?`8?A(3ngVd?-GNJR!AXkqu~a!{s5k| z5(@(oq?@~47}>!7KWQfqNYbu3_ku1MEsQa2E<^J-c-SKZC$mb3%LMl-nVtqE8!cQ! zcyj=c<}DZ7Y%nAM#8ya-%&PI* z=ewVn+jGC;9hrhv6W>G%l6l*}0YvtqW9+hkeU)^Bn>VTYS^9 zzAe#IdkPPEx|hP`y59KNi(II3pSCkW!?Rhz3z62r-K?x^>>xE}V}J5Hp0C=`@*d!$ z@FhY*Mv@qSg7AT_AOb}YP7(+KXO{5_9=gL=;(51Y*tR;uDzjwI-O0|Pzr$$tYEQDR z=$#IR6i_08ls#Rhzbmi}h;x8wAeA){72NE`P9k`d`+Km4Qw3)}>nrQf!Xireh=h2- zfCpyEN)DMS3J*n=(|y9iL74Z~F#DDmm;mr(9*|LmAwORHD?l%Siu|>zW+^eyTAqQN zM6+5}DRD_RF2X zwlEV%o`J*JoCzeq^$m@5;6#Iz#SzrYuA)us%X)ZdaU%IUAVP#(%8E?lv22buv9Lh} z{FAvP;9I=kToTM!yvwf4wGgq*GP)X$WZ0QrknK*CuI??%#g1;WO@=TYM8|SRS;Y9c(graH!2ys@1`C&hZNY_la7vox!t{oNs%V>p4tGW1B4A5M9{A46AF z@*$vMmP&c!X2<~=Ai*&L&c)6(QJJd;W~*r&bN~=_7Rph4If@0%QZ~<3$mGZ&fQy~5 zKTd9dRHZXw-$HXOcLI}=ETX2H7bC$gE3_BM-cd1lS=uoXYbQqxkX=^5Zl4Qu**qgQ z-@-^}m*o#4%K6Mi&KSv=Fev9dFDDZ@E#wDD26(`V5pOHfjagNdr*KweZ#28{gZ}~9 z%{TnkF|$V*k;qi+d4V@*-~wKnTo^Xd)&l%$Z|PA;Hmyv^@|z zU<(4NeWLWj3o#+l3uW7(S{k3_#ofU@w1~2lJCr4!qv2&%JJaDIajZf*k#qc)(;>DO z(aB(4Y^_ghsdo-4pRpy0Sw^eQMOal|0muj+^(%8U|AhO{7;;v6aJaK%hB zUN=kGf)?#yEe14%A=DZ8(OE16!fNhX^VAGuvY3>-QG;+M!o-&0TJewiaRsKyUy~`P zvW$A}#qnVqP~6`jy`m?DlAdnv6q49b9a>5%whBT?&3lq@>^CQwXivZsjM~wYKy7=*wE)n6Qv!?7Um&-SE-2&)#Ev}&nA}Ffs>$`Jbc^lM%Z`S30$Lc7>NA79{}{bDW>q(bSHaE}+CEnI(;(JC?GfaU^3BJD1E7Q(r)Kxlaevt(`s zpK*T9F2`eP%SzPERBkrTz;RX$2IK?yMm*z!o?KtTm&HOB=4f))itt^WNU1CyXb`22 z2~MoWWs-S?xx3tw4)h>jpK$nuLFvZWCVI zpv7GkBbNnfE_wF(j0q$th{*VwGjAMufE1eFeh2$*SOIq$c6k~%uyES7?Lk}O*fKE7 zkhgQFhY`4HZ~qk(LX58XYj**LP{q@XGtm+ySSs#iu04f=P}-0Dcc>P0A)ZgK?X2*< zi7zv=I774ZdVzU}w7( z*=QYuzil4#Wp~;$CR4~R0M6zIK2na%hq%3mZ7TMRtI0tmvdo1j<7IXsvyDz8vWt$T z350^6h|K##e$o0IYT_d(Ea$nYp6Ps=JDjkPy_W0Y!P=#yhPNju4UnyVhtxt$YkvzD zQfNrR-{)}%Z2qIzDo0KlvYzF}h|mIzzB#fAh2V8~MB&EQbW2lMDv1yvSVlW&EP{Msd0@E6ydL`l z(t4-o7U)gC#S=R>47CSS1ZWVC9OA-!xO;=#?~W{I62H&T6k~d@L{=BC5wa0KDTAUYuvo$nT1*OMDY5 z8d+O(%re%LrdaripQ3O)vIZT;`ZIh^^k2wy6Z9+IM9~*|6vn;gW5E&FWG>rF1hQxA$deZF4kX#lF~F+gM|pYgBd+@T0u8Xwny`2 zCter}%;w7?s5{xvDh8hMat4j*qR*X+aoP@xO%T!z7=%68*oaJY&koCg%+ZaMQf)iW zBcIj+8h;R{`4}pdh~fK%UUc>v07NcPCg@Z*&I(_QCv-@8Md#wBzHwRbR3_9cXyhyH zDdnwJZJc205e$}*oFqfqrAee>Zvq~%`C-}Xa@*49|0%N7%`vPi^7fOoe~}&5{2q)F zvSfM|CM;dV2l0%(TosNSxSqYBJnW?nXB62^r*IdVtm~JJ2l2%I*bQ8J1W{*T(R3Zw z<3Be_#Tdw1>3smLZUYevFrq_|)Qt)<(HY6Ui*kVpP5~!LsjM)NqRV(DE1i9LVqI}? zEt12|gG&60j=%$*Z9R4w6nC(&{0C}af(6i%z+W)i8+^!L9%Ty;LGR1as)Vu84b8a$ zQiV+j=+8sgkOorKuA%(Lp@m>0Y|I9^h0RuRJa5Ib2hlr=6c6xB4UbCC_g}6X@B5f}rRc)0%cfUUESPKVRgsima-(zp zwJZ+Y*N|Q^BWZ9xgk|&At)(dJPaK#a;7kRrbY683D&X#muB>Z0ZO0g4_7t>o3Qt|} zL-wEmdhCzLvS-Ys1$YgQ#l7|j`c%FI8q5);jbf5Ncb0F5k2f#)eXoI!0eeO{X?_Qg zsBd8@_JA;c2pF$0fy>@T0kMWwYOicV5>V%t(~LPfRu?JeG@IQZK)ifQ)tITHAC4eNm)}H~4YNE%!&Bcq_nKTx=1+Xi@$hDuy zgXSZXWt^AM`I;J`b8ff+>7X

UgD@&dbo;DvWff%#Zvs+Dm_y{QBFc8Jl9uBTvu5;(waK(+{+he!j`uu%cv z&7+fA88d(t2OTH;5tCp_LkO5TO+0^B8m83fv3F2~Orfn9h(|D-A*QPVJ#_Ac;K6js zX@V#dZbL$~U=d*7?J=`Zo>1m_tlb0MLxn2`$}o54pj<^jmIs{%2v}L%tB#UvuY{05 zG&!qlVZg~u-1D`VQ-Sm}SGIGDvAb@s8lurn&G|Ma^_A{awe5;$J!yW&c)=g(jF^Do zs8cPsYPqyVQuX*AR4qOTk4J6DQ)^Ylz42uU%n%VK=PA&f{s?7&E&+Qu49Be?g{ggD ztK61)1I{1;N&VIC_CDMLOE(Zbq8KB}j|_2*c{0Uwjn7U4Knw~ndLujPu_t6l%Dn_N zLCr|QkEUp%)2#pYK#liMBkiQ6FGodGN!d5G2OAv}bAr*+(J>-nE~wCP3OJ@!Q(8{$ zhAGDQECuJM@2yR8(334r*xd9akU%VBAo70#ql7Z?4D`bnBsI8nkWS<^fU>aYIeJ#) z8F4)@gVSU8tJ=i}Tc3ec${njfG>k^t5nTo(wUkL>wH9KWr8?jvN}Z7lf-?QC)k25*m=$ssq*%lvc>#ivhy zY^>6sv;)t|=MO4kN7$@)OER_~N1kO9c4U>^^vo(eLEIN$3;d}9y}b5W3;$6c_63Ih z=kUNuOsM zv=^dHd(i&?HAFrv0t$!=4j({#L;(a&YM-HF5Lv_N(n@r#L~a(=pW;4*dh;Li zV3hYt_tseRH*`g?+Y2m=yd9sGhgsDKr(;D|Y&)YW6#vrL2aXE&9yQ*Qe zW=D18kAm{*F&%w(M|-dE(jOD0)?*8i+nZGZPa?y(BAq?Nd@OLC7?l`WCc>r z7R3B{IRj!Ge(RL`oqM|F3{&J8-!?KRXncgrjz0CR3rB;K8+h;0>2(d*KefY$JCSab ztqw=m!X(Gbe8mSZ>|D^xbSc%qCM$;dn?TX3wRF6&vI28S_Y#bAc|fp^(b} zi$I#kE|e@`$ualLotH+b+k7l1!`g<6w2cwwjsjc_Z5>L=I&jY+_fvFEP3$(HIZo_O zWa_bd#DXy_GEUEYaD)bUPl}(< zQkAMONQjd^M+VADP~ciIXD}veHMt1}u1d6{KPe1H4*r1HL^04}$CGmu5Sir0K0&vH z_Oi9@N3GyyCHKZh&xhyEF8XFtQebDf6ytmwD|DFADd7Z5Z<7{jyB46&HMpJvk6!`` zuCyvnl=Lk-8{rdkOC#-`7WGXFyDkQPxUwWXyE=P-tOHcZA*}|mI`K<^S1E0EMYc>j zM)vW9ofUxrT?@8&2l3+D&j7Jkjf40j++~= zftlJsKLK7qnSY(iPuuHMWUbn>lDq-}QQuhlck~1O#(8R} z0g-5$Rp?}i8XE^@h?j#q;%DtSNFG3Fg?54m)>Zqbi_5T@2}B@3q_1(ar?JuLIM0wO zfS}n^=wU*A+uzNE{P|P+z-u&n8Wiz7h7e<^a3zonRwKYxB|!txarlI1PWNWM^Eak@ z6LIcL!tc3_sSI?^I0rIUAXlu?z&ST#IixsI<1TAViCz?n7Br!0#+b^8laK_a^8}{0 zG+}O_F-3b~TvRfKu0=G@l5b)P`p|iB8gO2?vhr0iJ6uG?+!Q#IF^sRbh_k_w`wJ)z zrdDaEZD76VK6{GX*4(Q%(b(8ycx$P3$o$ZUqrr$Y`U?x-6O&WU??=*&^HQ8UbWD!u zdcp^b7JIYd3M68oVy&;clvmd1PizH}$Qumi3S?AP_ICJQFaX;sfS6wlR$roWZc7x*o$}&*Q(uUMK&P*NeZ-8Ait>3b2>!Q20S5K zjhmwgf;N$0000I67I1BSSChupFridGrqKTz0G>-~dnD_uJ5Cdr&WL@g> z{}h)K_#+wa!baLeyOe^4Zph2bd3JaKqPuWGNQF%B*^|fpRaLFLSewZo4~XghnwnOU ziOGF~lIyIFx>lkhw{g>0J^L190u%>G&3PK(4me<~JjED?Z5bhF7y(0O29>}F8;Xs! zC{9#Uq5F`6@^C~e{@dyz{5Oyk3I=6!o@b!AL z7{ADj&V=(P_5d*aX>!pbr~(<#R%FnON(XTbbpX@aDP%@kLY?d0gh67lql0*Wg`+7P z=5f_gHS(}pGRYv#Sk4DFh+RQHRXcvY=_H8lbt{|sXLO5X@ra@bC146XtdNuBB(m`| z!6%2SpB+8FnA1G?G1MCYdlRGV3g9? z$n;|!1nq!krL>`E&}=`ZbNLdACbd%gVLss`TnJfnFKImOCt`P}H$UWmeNlWO3RWgh zpeCq9tXfg3MV_`u@9bL2O*}dz z!~Dp%2sY8j!x)0|jRk>lr_&U76N;SSh0V zm8MqG4MJ=FY1emYBeY@I^6D@7gV8SI+DrhGO3{drPk@3--(Tt~1N>=RQz+W>;3h{pDIY zfX5>a7{ZM|g)}+*1rSh5Je&fS0o}OF;|D4GV+f*eI|Lydkd{+q7`N4)Nx7%NjyMW8 z^qfPmb}hS6S%_csc@KD}E=qug=`FF8QFVVkB#Jno{ejIR^H35@l9SEeuG<`ITO;OX z<1{Za7tLySLdFedDYq?a(IJhW;TOp~eGEyYYWk8N~zlYP`U!+sEYf` za3pA6bQHTI$r@o-<92FXb`5gin2=PL-NaC^dfSIU@jPQ_z4=JSJa-gkQ}N1w;aRqm z=Pn-3#B0=Yj<=QE+be^OGNn8-MWNl#+sc66{_1*rG?2x8+X?sYaOul6MyTF=G-IA| zmiloe3p;r6^GZE{J zq#J_MMCD1rTmh{r?2!dh*Mcm3tu8sRbaH(1xD`V>hiJ-zk#>k)J>nrJsX0CNl@aLdphpyV$VW@c;kHT0T z;c6UKJOC>3r=8GK^+O#vzj9X}_i{T+rT*Y7iV4+nru#U%3-p1LOig?us0HKwAVm^2K+NsE$LwhzJ%Y z&c{E6F!^e~KwDVYJ0^2kKumLM0`N!Y(X(N*jW)SRhf!URI^MkkXl1fJpWY6Z;Rp%p zp{@!NNNVTD0)d1>BT>wGO6Gq7u@y-NYiO+gKfze}e}Um00AP3RDLfrZ1L6&G>Nko! z=Z=$ZVMj6!3dAEERR?Z_0*fbr#S(sx2H-ICrcOuil2CNHy0JlEa$bzZW3r5)YS8E6 zG#IMIe|LuI{5a#&po|OJvcg(}I|h9fR*JI^QeozX`pJ7y@Jykg48zLQ=&y|7DNcLz z@jdPCxb}Agz1s?+SN#g8FNjX$t=6H&!(0k)Ji))X_xa7^yDl72i7E{reA)2+|6Byf z|C0Tho1?#UBzMt};UQ*OyBZH4N_~=wy(Np8)>(RFTfaEMddJgifwJf40f6Ah(Z;D6 ztR0pxbJf;EnV5mjBC9ad>=l&gK1d_%X!sU@!*zaq z5ky7e@IMY$uR{s zEeE&=1EWJlx?KRG7D&^cG^cyVG-!qz(jG$lTy90H%GI2M1l;54{bB)@W}m}Pbj^h4 zv-saUWzRihbn~yLkO)FbL%7w>8)d{~a@a$EC~vU2m*L=Qgz-!sCB^%s_{;Z=M?Z@Yu#9hf)|qPTpdSi_i4Giep|Hul&~VTX5}e&Z{_YqrFV0)E%bsxYbbK z{5iU39Mw0UZMJ7v`|3;fFC1o`yy6F0=0}sACkMj)N7|P-CY<&5z!)bBz(INAUOtXWz8pa|hcwHZc z16UNK`mJ}YL;B{SSiy|mia~qa!x1z^uk=nJEIV7(2x(jY_f}6ADsc2;zLe zxf)SBM)4U+WC5(`_ZQ}n_b>7@Q2oOTNy9s=`TJ02UqKWz;Fm+%d+-C7k6qMoohCn6+Ffr?Kdo=-vrbsc-fsAPgH>_B zYOilk2I{SfH}p-fTNOL-BbfDe#T)gFJGBOV^G>S*a&i(|^A~|Vrd*QiJ9oEv)_Du! z>0WN8E;9I=XsktBl?qWf+|g#L(0C}FC_-~J(&x@C-tFFMFKs@6pW-`lk=6k04!??X zK`p$B0h}zysh-9UUxiUc^PrLD_^1Wgh%aFZ2A)SqIDpsG<8+Vs7*>~Yy9_*>+Wyl0 z24JXzf9M#G3vhbn|7tyWQO48kl-~4Xv{P>`d*eJk_CvOet|Yo3K(I~!j3p!Zo(lmI zfFuYU>JfPY(qdH-c*BN2BE#@|6!PS*BU6o;=CbZF#wZoO@Q8LDi0Ot=9Kf-^9`}NK z)?lbKgbdROnSDbRk7et>LE>C@lq!zf^MIK1Jt&!MJW)CiBVX;FA1}jjcG7R;)Z29w zpn7YRI|P5s&J3#~`6#O3WP-ZGF^cK+UMy))3deG{puCz3B@Gp=|H}!JMRn=dVTi zovB|alTGU>^V~Baq9v+(8XV&|3fvRPpW_*2bJ!~D5Ik)SW?@+;>gU?7Wq#$Gy{u1LG-oN01S08dZ)5rsOSACG$k z;%FNtZlV>!L~rDzEmiYISZ_lnU0lEEAAl`I_uLRVR+|damEH||zGed9sCp=Nj$^5%8C@+*16jSa3#}W&PwZ;=HW;aTB|Y$SY~Qr4bl zoZ)HBLE%AqTSxCqZgK3tKRDg2aw+JNz@^Ze-UF?0?ap=}eYsJH>vlTvBwM^%C8zIl zT)ARy-GNk1Z~76^yo+Cf?ZCa?|Bi*Ds;XYOm@ybV52QHWy)M)E9vwQ*HvNJIy?-mt zO!&!MDmO02y2k|`RUjKF=TCBeO&oeUm(Cd0%Tqs}=Z^GBLWEP!^C*c6j-*itHq}jC zP)opi>|&ImVvECspGzZO(1pj*1%AsHdu3-~gGt6_b0T%iYOraTQc z{B^7^0WK1|WxN(8??z)^!y08$&i}keo1b?3#12w?!&t(@^m6{&5CbmkP=HkgS+$KV zFt9Ul!hxMSnp24PYWH`Z2#e~OGm{R?w~~8NmMY04lRcs;lN!6Aj_6kI3b#I9ePyd?&mB_u{jm$gL?`{3`BoH z=ZdjZv$P2zNezmM8yaeii7;|IWS(Pn?)n%?6U}NXFx&oIeC#IU&-mrn1{A&?vt>4g zB?+8u4<7IK?LjCncg3)2#qI4*K;N|UDpk;RzH_`Aahe?O_r&p=GeD{)K7V85(LVCJ z0+aLtcqUj-S@>niTgDf-4+6oh=7}g~=l^V4s%bTAl(goh5t&n z5}y8}?KFI=g_O?<8Lid%$WTe~bkdg)N^>hSpnyMQl{nk;O63NM_E@X)kYJtSQ;uY@ z0LF*a0eqK83;I*z!G#rZmXrxVO3<;T>YgJd&1_)NbURJ}CDMZRhCclTLb6_9k4ipY zbcy|fAQ&f3MW2nbK)C$M=k)lgaU15sv2*CC%jMqB-_zew)HyzddSdZE(@(#*@^z=NJ4J z?j&hui+bcu_Z#;7yi03?zTNe9{-}COFj)~7ByX*>D{yEn=j?h*5^Rw%;Y?Bco+H@S z)GVx&o=b0=g$cVBUAUr|OkAG-)bcq`p?}g^y*1~l3|p%BcDZ|$a0s;cO{qOc>^KQn zEWpO^!bQveT_3q0Nj_TIv*%swOnyJ2=53)Ve#c=OjXBsA3xBzo%TcA)k1GuD7TuzJ9v5HKS73{Z^Pe`(~E}K z&u`#+Sx(XEl4JVxV>o_=v)Bk0_>RMhv|u#<*2NZx_9D*O(40+TYFxJWkB-r<{P-fS$x<1B`; z(M)A5zB+z$z+ARP!=0B#o+TJyiBvWHsNcFO*^V0ern6ENTc%sDStsmei9JWMyWIp9 z7eC2-k(RRkF30T2w2TDKJ6d)jkjF_!y@@wsn|+!14cOtDHZyvTb)u^* z$o)j-hkfSZp?mI6B+J32hSCBPqlq5-2wB)n4Fuo&??5SilON>%cXlZZnD&F=rRNZx zf7xbk8>+v8YuIDj!{u7Iv;e8bu6X1<3~u>r{!zU20zBuvheni2HO*yhVA~eph-OMe z7mO>Etj3?GCX^h*kIz9wKeZ~_aF${SBp1S}sx4>NjO}M2SpOybVx@=*jQtZT7^)7n z+KWED!S!3a6tI29nD|emiSFpAl5L9uMcoALW)xFg}NQtVwEAP zywS=`@=9H3lwFo<1H;G`eR^a|$qs!+J6uFWa)8}N&lPoaxO*&DyB+$DlMQ9L(cHLy zw6At!QSFwZ+BTF<8o%zOZ$CfO{hzK4{k_?1}_dQW3u~503*o z%oAf44{!e{%RDm9JUQm!@rwB1FHS>2gRnbJQ=GOQ^(UVOA0V1#K`v37pPXggl-GWL z;w)g=?hNCv$3`~TH3-aZ0Fh?1I~1CX#TNoE(SBp2-o$&^^i7jQy2AFdje9=GHWKb1 zImq87x>Nt82aYh1GMlovM(R9bGlEFUH|NM{+!8Q?V(R8qN)0uJv- zaN(grm}g|0FB7na8KR;{@&v*kST8CIa+?4U0R^m1p0562oY^yuyCHds);6AvH%+3~ zi|zdA0QPW$Lb$oyG?8XMPoOsTe${R)IlgFIe1;-YWMlL}eN%=IXc`IBlO0L8Z|R$x zzF9!RfHgL|F#cHF->7eT$fs`(XIREM3#XR*^u=RA{uLGLVDO5L+ZF4>ASqI;S!RCn z5g0&o+4_;uZ(yTGS7a5ewPreJ;de&*MR$}ETiC@if0Fm9q8ROhTwH4#>*6^>EbQs5 zSWBe;u;^T}HHzTum1%1+P4V+jK$BQ}tZ#l&Bib<7<3F{HRlYZPHQ8F=ixNd$Kn=f$ zj4m1Le8);6&n(OVfC{Vh*TNqeQC)nVV<&U~cFd{{^H$$eU1x z+7%n@Id-0HY>4MPZ&l^PM@v<#GyVdaB=3Wo&=p=2{|SX8Ua{9wl7Ta#W%-SrwAr#N z?*Pt=av;IkQpROrYzq*1$hT_Oi8$f(5iI}TvicA%Xg|*(I=?_T!mhxHrb4S?J$@~^ z=^`{6OW3vR(6$~she+VY7J{dkV%vyX9z$D%eulw^xMFv07$pcj?w1BiB;Emm#V_0Y zAz6{P8&9(r(Evj^n~?;|j~L5}iuEx4h@>pPsQ|WL--N4`%GA8EEZ;6Gq{J-3`W)34 zf_Y*8*bVK?gy5&g2GL%Q4eq|d!da6C$;^FR-!u)o(;_5!^r3E2fC>U#1% z6cn=~2=6}=;ck@py1Cg0D+ruNQZfOCicqwF{0?7PE~qiy+6A;N(qh8bXfanQX)S1R z93uBuXaUQWCdc@0;03((O_e?jCi_tukbAeW%SUwB4XQ5M2W!yf{NyhYLg?KV)?>qf zkyV3kWBt#+lH&RVvQ#JCOVNV$6OO*gWk6xTxJEztT2INs{R`iQuBE$6JkTU+!-DwA z@_`VBMY|?njiMYuj}`4I>0WeOQFo>cvshl7?Ky>=(c3X56@6yaer1TYjP22KWW}%d zt;kMQv`w>WH{f;ou&HXK&VWIL-fu|Vl#8*;d^A_noBk29DMKf$fH$H?f5suYqHVy9 zNPGdkR=z1lP1NmoPdV5q{37cYm9h`D8;Z7BGBn$@>q~04M<=V%|4!Jw0Tb76L&~5# z85Tpsqr5l}E-ofqFFi&d-QN%*%BpMDJvZbs`zLgn09FX=FJw$AIG0I_XxLb~CE>?`V=4JYIc1!@QG zp|z!f*P_9q9VNRKWfi>^p9c4-&DtTr$9ZG-vMrEq4A&StEL*($smVJbJ{UT|8LJk| z&RZTt9Ob59auUdEriMP8or-KRKMABN;It32s?&g8_19+x3r8w6DGR&kZf%3>)jx)u zNnkiWf5u4K@2Qx5@KIi*E&Q7m7zEpaFkPkES938+LSn@g`N}XLEJO>Rsx<;{nHub- zAJ56FeDnE#|NZ|%0iL)%e`;8>9uI2|&I$`p2J>aY-$3i`zmfjcl!~Z;sF=vRM}PPG+|PdA_aD2T+1>BX%r)1{Tr;zKc6JZossPsrxDmjO z0q!B-W&rmTaI1jZ1spSw4>rJY0nP(r32}kgLaZR(z}69B4H5aXcZIk?%pmr_O7*`0 zoRxrB08RoZd$~XBzt5n7FfI@$z=;4c%pmT7By)((|ARyb1P_Gy?^OP#>;|N04M?&D zH1G!GQRP?yTRY&`29WIv?5%*k?H}DN09`~NNFZ$oV08jyQe#jxa0Oyf!%}l)4uo|F z^tS|NXa8ktc;`P7sOgCSeohciAf!F8Hv>on*gF2%TL4E6fU^d|QO`U4+1mnV{z=*K zPg$7*TM@ufwf&pQf9Fo}k2X}zsA2w=l^d`Z0o;v05~!L{(-i^smVh?YoHzkmdIKr3 z04wQFEULEDkXC<)s+k+We{)Vfz5s-@0rqY{%nQI@5S#(Rx&k3w0V%FPEV)0@-GK9C zh%QhIx{<|I{C~#r=mw04@I31T{sfZBR>#T5?qV%>YNOAuB*S)lR78K?2U&0mmXh zTJC^9^~f6tO+EfkY-$`QAP%)gsOPELi2$}pwK!YgtQ<&-|F1R$d>8tE@+9)FTtFbk z5IrCzs@@RbF+R1XA;1|lpsn_wqrZEyh9(dhmbCrXEWGy2$<6N&w|Xbr2v=&VUw< ze}c*ZM_zw)x(<}bjX&p37{MSPY+#Vj*MLc-t}w`PLl{H{(4X46&_KSZt&O^X&nW<( zUooJbw>7sxTPQ003G?SzL7sa2e_G^#Hv-TQ3@4{y(-%wS@oMLT&B;H)$w9k}F_=)RyH8NZ0wpf9=m4IHuM;G*r20Wx87a_jD`v#goI}rog5w+B*WvvU8yaf=Os-1+rs|MiMSs+F_mX2l)mL|X< zixH5jG!RPqk9caY30yV)CZKi5Xd+O4x|VJzODi*XdpDH5J<8U_(%j9-#n#dlf(1Nm zUF|R~mX^BCX6BY?F9Yhi%dVC#{}bUdo@nN1?QUjm34x>Z0m}iz=~!B$EbT4bED4t0 z)EIb2TQ^%XH%na`cQ*?sPe%iYE?{zh62<}22#&xxjFY{ErHi(in+@PeG@zQGE)MSQ z=7e-|aHh%v5JMrN&^o{Y5a~bR4Iuhx9bG(yOw|lyYj3G<>EdeZIUh5#Hf7X~Q-+;krBB1t}Qjgkz5d<2|y8ZiC?`}K4feUN{G-o5I{l*; zRW}Gwo75ioZ`xEnsX9{iV~218S{#P(LIfbfkP{FYh$2J{s1aSDu3aI)kYdO%WDP?5 zws1mZ>zH-NYv#C0HU1#t_A zOF&!<;vx_ig18FAl_0JFaXE;~KwJvq0uawW@GZavPD8`HAp?|QRm2R4GeP0AL7WBR z3=pS-_*QB9AxG|MXnqu=V64FWcnZir7sRO`P6Kf=h)qF!4aC<$><(f#5LwHV1jIrhhJu(2#1)qZ?|?P%x68z&o{sVv zW0`?Q+5_#Yt^aNch#!q@dVlYlgvJB`@d=fz3{M_D$&nM_N=|2n$jE-LTMo_N{Z6sr zs!jx0viTV;$bSVCU;E|;dzH)-v`7k&r{St|3B;E{tPkRO$6v7X;#1J#1!joyM+w?F z5F3KRM^Gf?*B!~&>{KtNp1f~UtIuv;=sYn6tu+PGFDE`+2Js4raXhq2mXJr#u0f!D zynR-+Z7peaM5m!CM4*3QfIEh0H*&)3C!xJEB18&BTO^(yWf4uBfkrz6`Ze;?C4o2r z#Mj;y+H4+Pfd@d@@b)DM4fI_Te(9(GQ$8;(ahD7vI*D~3>_0FQU&(U&1TlUu=g~^^ zJt(c!Y$(6ab?B_4uLz~@p;o;EA5`B%xZUEeZW^YDPvk9Ztk8pks}tF_#S!C;?j}cz z+#F4c2+SV4`dzCk?MswrgW=kZ5S`n`Idoc;Zqf3$GgWp?##=L|_zCcmX}fc*^zL5s z(xZF!=d^K2iD#Wmqhdl%%L}yAi`JrYj1S4+ndUiT6ONeCK2RZr8@0-h9!}MZu|ABD zSQf+gwXUlb#N7~0^$}Ur>SHIjOPbfbCDR?YDhKn%hZhH{Zhj}o<(q{s;Y}Jp zdUqn2#Zt~kBuyzY4TCcg4s0$o9 zNq~18=ARtb)Obp3`0P$l#uPNa5@=8Mh`Tf(mX@+tZhqEJ>UMrDH$2_l&*i_Pb#`_d z+KB+#4;}Z)G$_4CAm(PtH_l6)h8C9q;h9e=&w%`AL988Kmdu|z15I)I+rcF-oU`2! zeL(E%%wHS5Ep^*MvK!)ga~fKD4yf<>=^OAeed}9G)6if6Kz}1-uqDX$z{OIEF@C*;Yg^q^0x?Mrcwhu%$q&AnMV(`!yxBkZ^i;;q7T?Mg+t(eW~WV zJl$VC{^@V;>77(}akZl~b6uX}j>pgPl|Ld^Ogx!&GHBnQ-KB5(3zt5Z~J(~Eja)xLj&UM;^G9c(bnj`WFK-on6Wr6y-1Hq;lk zE5D4*moS|XoHss|wkQF=uuKP!cV517CqOO798@0NpS0y$f%@*Ijt9MytTZ}ntcmfH z(A+hE2_2UhqTc=7>O-Ic$go^{G|$@>Q20RMR&$&C)6mQup#0}9uRuWYn?d1eLH>}# z)7zX!XQAD4kbvu7x5q`EL?5XxnuInA1L^BB&`E>1WC3@uZAycv3fJS(+`d9qd?dYk zy@?GIJk1cu<(fLy|3k{rGjj$J%Q5a}RvZ2rQrACK#tY~-LDdhH4%1RmU+pY%SM4YT)Agx00U^BV6qukK87+<$VBff9UbWX6R+VNp54 z^DB!!1Y4x_@$U28L*eT(nu$~UtCZ3malMa}&&;Huw(E=c&LUFiwLd=@mN@OxJ`(ut z@FKBR zR!56R`8*Lsom2dC`z2a+@xgu_Cw)}r=!BC5EnZ?nPq%cN>DECn<^RIf?0v*V8>*eE z!F+XyD#x`w;Rv8qYQ+Uz9k=sjj`4>nUOtN~hM#zF-ZMm#>(<59EDmvGJ@%$78|IVI zhWi)pM$&a$RtR0eokv0WWqe59w}wslZu9Zo`@pFF&?zh-4o5)OGrpo1uo||v2)_G) zmmUgF(ME<`ul$y9W8Y8nbn#%4RX?HTN7h-W#Gg6$k?OywG0P&m_&Z-ZC#AIL$UGO2=G(i>>zw$!mC7bs~h^ zwr}UM6G&^me;-@BZT>(j`;v8f4xK&YbB;Wv2#B{;!TLIy*4TnUS(8!RR8zn zwPCuo&X|EjO0>^;OS`)<}qX1nRr%prU& zn~Wcpj==(EHHEPJOHJ=zeLAs`s6nb_sjnS`XcbO9Ycjs0kdH2ygbg$?+kLf7uFu7s z=OC4k*-X8aO6R#g^Is6U-bV^~nP^g;eM{G@*zcbCFWmXX~=c*oem$OtLj zTT){R?t6y;8WpJE{TWpi#$4xfMmG64qV9v9hl*Bi$E}*dzUD!0pAu5BcLOCzrF*!k zWQlqB*f%5OwFz9?*|6ZWxx&ZcrodFrC}yp?#>ngE7kI7C!@U|j)J6!6YMe#2w3hk2 zGG~<+zaD`lnjDOK#Oy-r+GM7(BIdQaq>A=kW%I`(?`Hjst^EMc%aEejpt^SEsMQ#{!aeF~c_NBN2-H7FmMwWl z71SW(zSu%Kr4mmj^eaj!`^-X{IDq~qL(?=7#08@drh+IAJ2WF7cIUOa) zmUFjvhwYuvz zvS$hjIadW=L~SgQN()WDNsH8db-z4)!5{*-o(2A1$7mt$(5Kaw0nsj3Y;2d}wK2@Q z=uc<2?%H5yp?$`{^+tt8A?#IR!&>kZwEQqcq<%jAMiJ-7-`CDDl9L0o#pl01h&YKG z)y=F{B-NCh<^3Y05-~IRgXPr53^XykRpH}X1CtXIjUqO-o#gh%We$*O->qU6?k8QC z5meCW@qnbkUoOd>rmZ!=JEcLZ!MrD{?Ay~iCnP>3`JtO)ux`wRCpQQnd%14eanKn! zN$JSRvMYBu{D!{Eb16w3N-8WxoRijFUZXqxs^;M<5kkoQN8Fg;IeS+gQDXNICzZRPzC3r~D@!Kp6+j>n7I)l9}%e_pSMQ47T z+*xSO86c4~A_$5fpeq<&fB?qB&A;PefUWoOk(?Q5#YG^!v0O8nm;Ze|T550NH@8oq;yD0QxxYzk&J&#l2A+Bs3vno)r!?(=St|MgENc zPygn(E1n%KQ47Sc0PI}=N;l9qr;!}^n^GsS9H+VoGv`Jy)SWvgj|%?qgr>?W;PtZE zI~SKQI%J~z?)trGIp5J5CTCiO8EDlka6L09$q`_+jM*Uo`P~BaWxtPS=a=0P?bAj^ zG`L#-O50YaVQYB7X^^cIYs;6vQ0k&)DjWM(pZUM~BuMKG{5$@DUb)`C8B%Qf?BESx z4{CsXPD#En<|?Mx2YlobEvkKj_BSplun?Bt%M&<0YhJPiOJro69pB)9s;{!1di1o@|O8HmrVEa$z!GESi zJiF0w^U7=s4vg3#Y%=nQitR7HPVaBDA9gnj_WhP{R<6XufY9F z@T$t?H;v!OIgYm*>R&N9z;>=%1ZoR#||&h-{7Y8DE7xqg6BY#a&v;C`gHdj}q|_M!Ne zsAb5?txtI4m>g1;nqFCu|0Q*iH0Q(KLzB>wJQP}24+2Tpj~Xc4oq>j22JB7vd;kP= zzh+ty$W!5w6Y7(M{7f6;IOX4jhGo|(?e?c#y~f9*3Jm; z&q5nNQ``GH%|dDDjrDg^(9)kkfBi=4^sOre*%fxA<^b%{;y7=s6pJfv$u<=at}XM*feXAvoat5RV^D=E*o;Q**Yf8&;CjZN@WrrsoA}gDE9TH+4)lf z6n8OLEw*&C$}A$qU-8ZHn$T5rk-EyLu1LDAiC$&VP(ceKow=Z?Yti#Q*xteRO$WNa zIsvl(Y0&R?Gax<$>fd=m;{!cNKI+rHzQg`BG&Kq+&#{7pcxa2-{-Qjo7?-#S8+#o}%A)M=S?ldijz}&@WVkxKwg>m6 z71~^X`~})6ME5qj=dkQk0rpcLxGKL#A5{3GGxJNwxmqeh@k1ACL^hDfG9#7S_mo~7 znpRcOzDC?9Hg`P@RHU%)h9ziMRT@hW<5LZD)tOqN%)aYKuqXljr5#Xz!1xKaPy9#8 z`-7KHk&>?&toznIb3KtX(08oC0bWp}qk5}N^~>j>aFK0UU3^8gm1nj!eX9I(_RDaF zS!hlr#I)hPhH8E0@RY~XX=n}?Ab%_;E{-ea10FdGO)LS}+jiqqzul?FUGc~UChzu> zz8W}p&*0M!_sE!XH15oFi(B#!;tf;Jl16M)+yyPZjAN1;-8|1psgEcPpM_gHS$PR+ zCtpzl69+L3CAl9SFWlL*ojR@=qn}2~y@El(-B#x!-W`s71?j=Wk&l#ztymT^vo)}> z=`D~`&?nF3m8FLsvEIEyF=HU7MBK7BDojxp`>y?Sp?ClvJzu7_WLuK7%g}N?F<3ROU`w~3dEKmwg9m$h=X#CoTohI@a?Z_ zB3u?|)NMfi-4Ck1w+;OwhI<<@HF_}UJm1Kk|Hu|d?2xuCSyle5@tv~sF z9$xzPZNplAF<%KNe?=fJ1aSd~i$T1Z*^zML0s}UpId+F2iF&h*A1#@3tipxhw95;R zUjO=yNd+@q7>W*e2(EH4?Z^uR#SZ{+3WyUyoB(1*P=7Q68c&8mTm%{~7(xB}G9&zV zmg+RLB+!s8j@tm56v@@!2J&J`{TFUo z%_OMSlxcOdb`@YlyyL<42)0+SJ%jBXY@c9z{{L%F;Q9sEKiEFN^$)H;aQ%Yo3tXSz z`kn^WZ+&ok2;B?|A%y3RDgw?)KzQ*MP@a_{0tu!zwJ%~zJ1h--lQQ>Vu?=<~AMt+A zklV3#Xc*ukw00hwd;dM$6jRZab3Zs15#;Y@YW|y1#Ho8uo!RLIx=Ct%_1EpZmLIP_ z3|~^>BnGvHUuoked+CyPh8 z6NE@w!bb?i6~ zmZXgPmG2=X<3cA&YSb;rtFP{;~HAkJ$+)(1DjU; z_YGia#D`xBrG7SeKzKJ? z-mjTr`o-1Nd@LduPY-_;t=h^)c3)EIbegL~Mt`qHc0``Wc22&|4wBm86SF%z@l~P& z5p_t9{X~iSnHh8S$optsQXij~+8cp2OUhD@B@;!G5?s#ztK{XK#R_rv+1)WETp{Dj zh{%14%B22p%iqR#3AItT*M9rO``$05w+xdFr_}lI_r3^Z`;H@Cl`IdnrBoNx^LTJU zWQw*K>Sn%2A%cmzr6XsuqYK#O)2jdVJW(_{Rzb@17a^6HAfu40-@p<5&`)_VTPp=h z?zM7#FucxJQ&$(sjP7jj4!hmA`r$rh*h`CRTr00choqC~Xuup0pxe!4hUrmnP1SS` zHB}_=W*@+2p+$bc_%h6VtB8(}AcR#%l!ko5DV#6F6bf}5IY)Drl=jTNf6SUcvk-Ls zSO8-GHV+ex>~2(Boy2pBd6c!BO0j;<AYey&PE;C@8zVP)A( z^HUnBv(VN#K!4!(7x|A8?b}#>uF{XRrZIW%YR-YuOSI5VN<9$A7F;z7jJWEX6rd-$ z8}v01(VOy7Ir-9s_l*THke18(#EuHmdQ;}3zFW`CmU^zyQi?0>gCegP(>s48yPPiB zrsSV&;|Ou;qGWo_-LALlLMH0ptVO&YNxSuX;KS1lElQ`z{TGMT8!f$8N17II=Ode9 z?h3DT@40YUt@!v0*P!}$f>*Ae`Bm7n{nJ-?sS4eJEJf7l+K&~(axW_v{D9S)g-jq> zNJ^QL48@XPWbo1aejYzE=GFtl6N`6WCE%K$mA^tND8$5ShaVq`+(AV7SzP7R9yjEw z*2!c4_7Y#yft3GZ<8|4m<9hX~Z!jg%y`LQa^@0+{$ zump|z@z)0(d;emJb8Ap8!c9YV|0X~m~?ZnHG#;jAML6Zd2=fwmEA+BF?P@3hK;TI!531xKdSx0D>rO*}Yd z-VWS?C&V$AK|3gWBCIVM^3r{zL_bfVS)u973lI8RWEY5t1}XoyS>gBKamyPEy7E1Q z#$ah58NTCJUJObL8Y1lp>5BEDJeD3iZ%fyW->v6jJMs^+(MFhEp3Zu-AAh1AQ`=%6 zileg~z0=6pZ?ifI!&6c?HlXbgt;pSW2*2#exJK&>a?-q=T-0ESl3LV@ zXCdu!$nwCeQ2FvJ=5|p!I%}PaT5arxF{isiP8YYiW$&{?DS2Mky&$`9T%06Yd-lAy zi0u+Dl-6nYF&ly6=lT>cA-Y0MnZ`d%<+^$0li0}g_%t6_!z`O7;YF#YKzM5ywq?6l zXjq}HZuv=Pf8{+Qtf{Hap{*Wo6DryKvCCG=O)H(tGTvD{`d% z=%ZD+&-n>V_=sCXv0toT^|QBv@1Vq-$&Fuhlj)^Du4iW5E4VLu3lmP~t#tIvl#mI{ zBD;OV6LbvmOZwqZjOruicg>#)?eJk&5v|G(3*Ot@Jax`My%65r$WiAyZ=ZTi1tLup zTf%qDpxD1|YrMGifbm;qm>)-Q3qAX`^rap0_K3OA)6?*I8iakvk=x}3IfO}J(=9|+Hm^O!K5YsU|ULYci8K*B3dV6d)<;|riS6o*BUQgE;syqn*7V7 zc8(8SX0Fn!&lngQA8LP1jKdV0^?Os1x9yf|kV*B8!Tn)UQDTP zz9iDg>zN$juyd1uiZm$pyhtm;u?@TG`?KH|AzW)X4jL z#_b_k^qCLATe#-n!jA^;owAA+ocK?FHbj z;}t|#h1Z*H)8K0hp>3MqE%%587uL2dUGg=q{_26Ge87>@3NPN^-Q`QbtSfAdpY75L zq?Ep!-FLWljN^Sqh6sujhO$ae<|zuaxu@N9=gSJYOkIlO%}kXrj3ME!_X&4m2*Kj@ zk8ii^nr+8%On*V>PWKWIJay`f)N?73=YdOo z?I|yVFy&7TN>7S(IJIdI*^BAUU%w9=j~IC4Oe0E=hlI?eTB-%Za^)n_CRg6v}v4|-R}e^O4_r2Ln5JEcap zqOa;KDjD4Vpf-LU6L?od_>jn_%PY)A;(1TS`OYYzbC+&f-TK+_I}g1(JQwf+-M=mD zkuUQ`zLhUs5gU_&OBs6l_4XpFY*^Rm2CjOL+$CJN4afZA+h#D|#o2bknl1;TH4cr2 zc8lEUy!knu*u`~CnHkBf_bu8ANgiy%r;oXLhRlbwBeSwjq52G9B?5-GJ?7thKMxn> zeAJkr)mVlKth*{2*p9RcOJTA2dvi38}o@-Yjv2| z=d9?PP8i7d_85#^6kU1fo}Alqdy^qb`i-8)=R9Xew~On zkzRPs$ONk{s}H}U<5>3UIyTYHu!|h)KI5j;@o+ClQ#4?|heC|t?UppV`k9a~!3S+cL(+Q?eDJpE})+%JN)UccUNUZe9dHfwwbp}di z)&9^J7NbAi5ZU&~qnjB1)zE~4ZP~f?6vf!~TL?MM^rKLxukmPBHTk-tjTt2}_v{oV zJr{qRf64!BbSXZ$!)}H61CPw`3NWC=+(31k-=aSe(0J&<;#`W2mOH%It=CZN604Vt z_yY;wtV@)D<=axHe)l1y((2;__zo}uCvqpNb2Qf=`zp;(a?fGP9zR#y=aRW?$YI-T z_Erti7JlBb|EYrNy*?jV)O{AM#2=G;Uvl;f(~QpeyB$1-*FP2XD*aSf6klX;xy&S( z5^-o~EDBoogMRthT=+v7RIb4_mUk2<7bZpJ>4{T}@ca`}`fIP2-)u=pW?-H2ur1g? znICuL;!#ngV7EhatUS+B#c!OOPB=OjeDbOlu1=AP*=k%P(BV_{sM zawl5CwTBh`NmYzYR#RPf`*p7~CI?Zul6|*EMdk)20?y{#oSte2+9>wzn~Idw zMW5|*X=LJ+EKQRqZy|)T-UFqBOP08>-S1~Zw0|SoMPz6#GF8lvcJhVMFFz$^TRq=! z3b4w+7rvC3F;T|0R&Nyh7zKtu@0hH_Jp75OjHa!(UBaA*x~0||z?e&lYFe3XsR?l4 zllWE5tSn7T+2j@Ac!H_^lG<1z5%d%rz?}n`FbE>6%53&ukaY%x(Sw+dVO* zC;b%Hcw~tO_;R7#nF2raiEbWPoNVD}=RhlF;fq)y!33T$LAOWsprAh zSA3>JhIe;n{o8*RlLC3yetx)X88oGC=K5p78xgFJKeyL^J1l%X0iGG+s+S+uUNtZ zYRj*-)Dgprw?EeZ~4TU(mG=wTC+S zFq!x-^FlSO6RZ0_Kblrr!t*1vt-PU=Y24;tbo<+Y`JmM20mpwvGlO_}W;rY}0Qh}( zof)DjFYL+$@`r->R1}xLVul)_JwVp(dg;IoE|5P9h;vPY5ksUtWOoaN=jEiVnb%uo zlEm61w0{t||68Fu7Nk+@_+Fw1)4o5r^Da}U5DP^) zdP%mRkV#C3-mt#tt2_+ymvX2{nRG3K2jrWZcZs(6mGOAxuKW4nBO)$cW!O1k0Re>% zT>9_l=TFZXUsC$2akO9`S`d@C@v+KV=I(_m0GiC8&ftiU)AZy|ANcJX$kz=~#`n0vo2__Sc> z&B4w!hb?;Yb@q9w@FnKMA%*aB)s&vVuRQv@tXj|guf_Clq-kY^maSfhF*oMna;4=$ zpG0TAepTq#%fH#vzi^ezR7mPNzYwk5bpI18C_jwfxwkxL39z>3uYT|wID9!w;@7T6 zNTKp)Dnw&W#)%Yf<2-T}HOZ}3+TW$l-w1$*nusG+#ZV0rhs#6uyxaF5)2odNo}g3< z&M12E-R)jO`@|;?v*5yG_HGYPI|upLDjMZ(oF`Rb^6vcfo&EmNn)%I{A7n~hAt{Qr zNKFJTKzj>6DoW~eE8tW#F_e)!vuHC}0wsrptE8+SHu=HJKJ)V3lSOp+?JT9m8RH;t z#=cSBQ?{gB+Dlowe$)5hQb%5X5_w6^(#i>bG|xK|a&__at*d^>MCk?A7_V@Jvm%U} zN*o_4*$(fxt|-DzVcSO7UJj~YBKenwjn9S#tJ7w3pXyAbtPRGA`8;U$CI#jPyYa~O`7*pkX3A#`lCv{Wi)tQUi_Quf z-mQ*IN5{$JaowlpHQWtQQQ z_gYO3Nu?3#U1<&WwCLeUrfbi1aKDrTK9Tl+Lxr{1%`uOzh9*|G^hH-0TQuu_k`b)jk(8wU1pkg?R#vJniD*n z^z7ZT!oxsCuTQ9O73aWDvBJ(3`7tMISw6snKd1MJ%f^c=4DLxi4;#VdFYLDY2&0|{ zd@0ZPN>hc);Y6L2m3lQ0c|4ucyLA~=v6Od(p2HQ(Q}!q&vtmT6PG!~fO!AJ3(*VUQ=T`R_N``8LtB1hpDLdfVVtEy)| zbyL$}U8oO^xt5z9FX^9cAsU0>jjUIp>BU|aV!5dag>#+w`sALC&{{>K(^CsU)(=Nu z{V($QjUQsnOPlY;+1L!>`rIk$GTAf^NH zArLcw_)D>-;dB@?q3pe^p83JxA>%=}(SaL$=;#;NJ_GfrCpuz@x&ECG$e~=D9Eu2E z7ti}oc5jpz;EPIS)0*je%mt-_&~}()a$K+mFVB0nYtrc_ZlK4^(aBQ#V{Y{ly6&IS zepjKC(KWZF-RJo=e@)1;oUBMjkxK?OKBezA$n-O$O1~RwqcrOWq3d*=H3h4*YwvHk zVe14I!(Otgy-?kcn!;R!;L`M)`nh#7878=UUdQ=P;TvtVl8a^DHB3$<82$7*f(dH7 zOme#wXYGPuma4Ck*9uQl=iZaC>kiew%BSb-jKFH@{{>?`zRRBjnhr%?-~6 zgqr`sF#355 z_;a#g;eYe86x{9Azv54`P{zzZ%|eSMf%tLn{SOV7Z@%w-f)5h1cxE+W<=-G*=((Q) zyuW|~{DJou3g9%@uuH&v_%`7Afe(hbswdgxhMxJ~?;pgr>v-)84#LZLa@a!CSg%() zKa%m+TqH+@e)~Q1?Uiu%!hpvzj}N&`KxAuEgl5^}%}K`{dJ}xWx}y9!xwE#*Cl

5COeJQ4^1zkW4d6)1R#{HlCF3V}_1iYVy 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⁢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 Cnu1RN literal 0 HcmV?d00001 diff --git a/windows_xp_driver_ui/driver_xp.c b/windows_xp_driver_ui/driver_xp.c new file mode 100644 index 0000000..403605c --- /dev/null +++ b/windows_xp_driver_ui/driver_xp.c @@ -0,0 +1,1050 @@ +#define _WIN32_WINNT 0x0501 + +#include +#include +#include +#include +#include +#include + +#define ID_USERNAME 101 +#define ID_PASSWORD 102 +#define ID_LOGIN 103 +#define ID_REFRESH 104 +#define ID_OUTPUT 105 +#define ID_STATUS 106 +#define ID_DATE 107 +#define ID_ROUTES 108 +#define ID_STOPS 109 +#define ID_BACK 110 +#define ID_HALT 111 +#define ID_PAGE_TITLE 112 +#define ID_DATE_LABEL 113 +#define ID_USER_LABEL 114 +#define ID_PASSWORD_LABEL 115 +#define ID_ROUTES_LABEL 116 + +#define MAX_BUF 65535 +#define API_HOST "192.168.68.66" +#define AUTH_PORT 5000 +#define TRANSIT_PORT 5001 + +typedef struct { + HWND hwnd; + char token[512]; + char lastText[8192]; + int routeCount; + int routeLineIds[32]; + char routeLabels[32][256]; + int stopCount; + int activeStop; + char stopNames[64][128]; + char stopTimes[64][32]; +} APPSTATE; + +static void SetStatusText(HWND hwnd, const char *text) +{ + SetWindowTextA(GetDlgItem(hwnd, ID_STATUS), text); +} + +static void SetOutputText(HWND hwnd, const char *text) +{ + SetWindowTextA(GetDlgItem(hwnd, ID_OUTPUT), text); +} + +static void ShowControl(HWND hwnd, int id, int visible) +{ + ShowWindow(GetDlgItem(hwnd, id), visible ? SW_SHOW : SW_HIDE); +} + +static void ShowRouteScreen(HWND hwnd) +{ + SetWindowTextA(hwnd, "Transit Driver XP - Assigned Routes"); + SetWindowTextA(GetDlgItem(hwnd, ID_PAGE_TITLE), "TODAY'S ASSIGNED ROUTES"); + ShowControl(hwnd, ID_DATE, 1); + ShowControl(hwnd, ID_DATE_LABEL, 1); + ShowControl(hwnd, ID_ROUTES, 1); + ShowControl(hwnd, ID_ROUTES_LABEL, 1); + ShowControl(hwnd, ID_REFRESH, 1); + ShowControl(hwnd, ID_STOPS, 1); + ShowControl(hwnd, ID_OUTPUT, 1); + ShowControl(hwnd, ID_BACK, 0); + ShowControl(hwnd, ID_HALT, 0); + ShowControl(hwnd, ID_USERNAME, 0); + ShowControl(hwnd, ID_PASSWORD, 0); + ShowControl(hwnd, ID_USER_LABEL, 0); + ShowControl(hwnd, ID_PASSWORD_LABEL, 0); + ShowControl(hwnd, ID_LOGIN, 0); +} + +static void ShowStartScreen(HWND hwnd, APPSTATE *state) +{ + char title[256]; + SetWindowTextA(hwnd, "Transit Driver XP - Route in Progress"); + SetWindowTextA(GetDlgItem(hwnd, ID_PAGE_TITLE), "ROUTE IN PROGRESS"); + ShowControl(hwnd, ID_DATE, 0); + ShowControl(hwnd, ID_DATE_LABEL, 0); + ShowControl(hwnd, ID_ROUTES, 0); + ShowControl(hwnd, ID_ROUTES_LABEL, 0); + ShowControl(hwnd, ID_REFRESH, 0); + ShowControl(hwnd, ID_STOPS, 0); + ShowControl(hwnd, ID_OUTPUT, 0); + ShowControl(hwnd, ID_BACK, 1); + ShowControl(hwnd, ID_HALT, 1); + ShowControl(hwnd, ID_USERNAME, 0); + ShowControl(hwnd, ID_PASSWORD, 0); + ShowControl(hwnd, ID_USER_LABEL, 0); + ShowControl(hwnd, ID_PASSWORD_LABEL, 0); + ShowControl(hwnd, ID_LOGIN, 0); + snprintf(title, sizeof(title), "Route started\r\n\r\nTap the halt button when you arrive at the displayed halt."); + SetWindowTextA(GetDlgItem(hwnd, ID_STATUS), "Route in progress"); + SetWindowTextA(GetDlgItem(hwnd, ID_OUTPUT), title); + InvalidateRect(GetDlgItem(hwnd, ID_HALT), NULL, TRUE); + (void)state; +} + +static void UpdateHaltButton(HWND hwnd, APPSTATE *state) +{ + char label[256]; + + if (state->activeStop >= state->stopCount) { + SetWindowTextA(GetDlgItem(hwnd, ID_HALT), "ROUTE COMPLETE"); + SetStatusText(hwnd, "Route complete"); + return; + } + + snprintf(label, sizeof(label), "%s\r\n+%s min", state->stopNames[state->activeStop], state->stopTimes[state->activeStop]); + SetWindowTextA(GetDlgItem(hwnd, ID_HALT), label); + SetStatusText(hwnd, "Route in progress"); +} + +static void ShowLoginScreen(HWND hwnd) +{ + SetWindowTextA(hwnd, "Transit Driver XP - Driver Login"); + SetWindowTextA(GetDlgItem(hwnd, ID_PAGE_TITLE), "DRIVER LOGIN"); + ShowControl(hwnd, ID_DATE, 0); + ShowControl(hwnd, ID_DATE_LABEL, 0); + ShowControl(hwnd, ID_ROUTES, 0); + ShowControl(hwnd, ID_ROUTES_LABEL, 0); + ShowControl(hwnd, ID_REFRESH, 0); + ShowControl(hwnd, ID_STOPS, 0); + ShowControl(hwnd, ID_OUTPUT, 1); + ShowControl(hwnd, ID_BACK, 0); + ShowControl(hwnd, ID_HALT, 0); + ShowControl(hwnd, ID_USERNAME, 1); + ShowControl(hwnd, ID_PASSWORD, 1); + ShowControl(hwnd, ID_USER_LABEL, 1); + ShowControl(hwnd, ID_PASSWORD_LABEL, 1); + ShowControl(hwnd, ID_LOGIN, 1); +} + +static void DrawHaltButton(const DRAWITEMSTRUCT *draw) +{ + char label[256]; + HBRUSH brush = CreateSolidBrush(RGB(210, 55, 45)); + HPEN pen = CreatePen(PS_SOLID, 2, RGB(120, 25, 20)); + HGDIOBJ oldBrush = SelectObject(draw->hDC, brush); + HGDIOBJ oldPen = SelectObject(draw->hDC, pen); + RECT rect = draw->rcItem; + + Ellipse(draw->hDC, rect.left + 4, rect.top + 4, rect.right - 4, rect.bottom - 4); + SetBkMode(draw->hDC, TRANSPARENT); + SetTextColor(draw->hDC, RGB(255, 255, 255)); + GetWindowTextA(draw->hwndItem, label, sizeof(label)); + DrawTextA(draw->hDC, label, -1, &rect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); + + SelectObject(draw->hDC, oldBrush); + SelectObject(draw->hDC, oldPen); + DeleteObject(brush); + DeleteObject(pen); +} + +static void PerformRefresh(HWND hwnd, APPSTATE *state); + +static void TrimNewlines(char *text) +{ + size_t len = strlen(text); + while (len > 0 && (text[len - 1] == '\n' || text[len - 1] == '\r' || text[len - 1] == ' ')) { + text[len - 1] = '\0'; + len--; + } +} + +static void ExtractJsonFieldString(const char *json, + const char *field, + char *out, + size_t outSize) +{ + char pattern[128]; + const char *key; + const char *colon; + const char *value; + const char *end; + + out[0] = '\0'; + if (!json || !field || outSize == 0) { + return; + } + + snprintf(pattern, sizeof(pattern), "\"%s\"", field); + key = strstr(json, pattern); + if (!key) { + return; + } + + colon = strchr(key + strlen(pattern), ':'); + if (!colon) { + return; + } + + value = colon + 1; + while (*value == ' ' || *value == '\t' || *value == '\n' || *value == '\r') { + value++; + } + + if (*value == '"') { + const char *q1 = value + 1; + const char *q2 = strchr(q1, '"'); + size_t len; + + if (!q2) { + return; + } + + len = (size_t)(q2 - q1); + if (len >= outSize) { + len = outSize - 1; + } + memcpy(out, q1, len); + out[len] = '\0'; + return; + } + + end = value; + while (*end && *end != ',' && *end != '}' && *end != '\n' && *end != '\r') { + end++; + } + + if (end == value) { + return; + } + + size_t len = (size_t)(end - value); + while (len > 0 && (value[len - 1] == ' ' || value[len - 1] == '\t' || value[len - 1] == '\r' || value[len - 1] == '\n')) { + len--; + } + + if (len >= outSize) { + len = outSize - 1; + } + + memcpy(out, value, len); + out[len] = '\0'; +} + +static char *FindObjectEnd(char *start) +{ + int depth = 0; + char *p = start; + + while (*p) { + if (*p == '{') { + depth++; + } else if (*p == '}') { + depth--; + if (depth == 0) { + return p; + } + } + p++; + } + + return NULL; +} + +static void ExtractScheduleSummary(const char *json, APPSTATE *state, char *out, size_t outSize) +{ + const char *routesKey; + const char *p; + char routeSummary[2048]; + char summary[8192]; + int routeCount = 0; + + out[0] = '\0'; + summary[0] = '\0'; + routeSummary[0] = '\0'; + state->routeCount = 0; + SendMessage(GetDlgItem(state->hwnd, ID_ROUTES), LB_RESETCONTENT, 0, 0); + + routesKey = strstr(json, "\"routes\""); + if (!routesKey) { + snprintf(out, outSize, "No routes found for this date."); + return; + } + + p = strchr(routesKey, '['); + if (!p) { + snprintf(out, outSize, "No routes found for this date."); + return; + } + + p++; + while (*p) { + char *objStart = strchr((char *)p, '{'); + char *objEnd; + char routeText[2048]; + char routeLine[128]; + char routeStatus[128]; + char routeStart[128]; + char routeEnd[128]; + char routeStartStop[128]; + char routeEndStop[128]; + char routeLineId[32]; + char routeStartTime[128]; + + if (!objStart) { + break; + } + + objEnd = FindObjectEnd(objStart); + if (!objEnd) { + break; + } + + if ((size_t)(objEnd - objStart + 1) >= sizeof(routeText)) { + break; + } + + memcpy(routeText, objStart, objEnd - objStart + 1); + routeText[objEnd - objStart + 1] = '\0'; + + routeLine[0] = '\0'; + routeStatus[0] = '\0'; + routeStart[0] = '\0'; + routeEnd[0] = '\0'; + routeStartStop[0] = '\0'; + routeEndStop[0] = '\0'; + routeLineId[0] = '\0'; + routeStartTime[0] = '\0'; + + ExtractJsonFieldString(routeText, "lineNumber", routeLine, sizeof(routeLine)); + ExtractJsonFieldString(routeText, "startTime", routeStartTime, sizeof(routeStartTime)); + ExtractJsonFieldString(routeText, "id", routeLineId, sizeof(routeLineId)); + { + char busLine[1024]; + const char *busKey = strstr(routeText, "\"busLine\""); + if (busKey) { + const char *busStart = strchr(busKey, '{'); + if (busStart) { + char *busEnd = FindObjectEnd((char *)busStart); + if (busEnd) { + size_t busLen = (size_t)(busEnd - busStart + 1); + if (busLen < sizeof(busLine)) { + memcpy(busLine, busStart, busLen); + busLine[busLen] = '\0'; + ExtractJsonFieldString(busLine, "lineNumber", routeLine, sizeof(routeLine)); + ExtractJsonFieldString(busLine, "id", routeLineId, sizeof(routeLineId)); + ExtractJsonFieldString(busLine, "startStop", routeStartStop, sizeof(routeStartStop)); + ExtractJsonFieldString(busLine, "endStop", routeEndStop, sizeof(routeEndStop)); + } + } + } + } + } + + ExtractJsonFieldString(routeText, "status", routeStatus, sizeof(routeStatus)); + ExtractJsonFieldString(routeText, "startTime", routeStart, sizeof(routeStart)); + ExtractJsonFieldString(routeText, "endTime", routeEnd, sizeof(routeEnd)); + if (routeStartStop[0] == '\0') { + ExtractJsonFieldString(routeText, "startStop", routeStartStop, sizeof(routeStartStop)); + } + if (routeEndStop[0] == '\0') { + ExtractJsonFieldString(routeText, "endStop", routeEndStop, sizeof(routeEndStop)); + } + + if (routeLine[0] == '\0') { + snprintf(routeLine, sizeof(routeLine), "R%d", routeCount + 1); + } + if (routeStatus[0] == '\0') { + snprintf(routeStatus, sizeof(routeStatus), "planned"); + } + if (routeStartStop[0] == '\0') { + snprintf(routeStartStop, sizeof(routeStartStop), "N/A"); + } + if (routeEndStop[0] == '\0') { + snprintf(routeEndStop, sizeof(routeEndStop), "N/A"); + } + + routeCount++; + if (state->routeCount < 32) { + state->routeLineIds[state->routeCount] = atoi(routeLineId); + snprintf(state->routeLabels[state->routeCount], sizeof(state->routeLabels[state->routeCount]), + "Line %s %s %s", routeLine, routeStart, routeStatus); + SendMessageA(GetDlgItem(state->hwnd, ID_ROUTES), LB_ADDSTRING, 0, + (LPARAM)state->routeLabels[state->routeCount]); + state->routeCount++; + } + snprintf(routeSummary + strlen(routeSummary), sizeof(routeSummary) - strlen(routeSummary), + "Route %s: %s -> %s | %s | %s\r\n", + routeLine, + routeStartStop, + routeEndStop, + routeStart, + routeStatus); + + p = objEnd + 1; + while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t' || *p == ',') { + p++; + } + + if (*p == ']' || *p == '\0') { + break; + } + } + + if (routeCount == 0) { + snprintf(out, outSize, "No routes found for this date."); + return; + } + + snprintf(summary, sizeof(summary), "Daily schedule (%d route%s)\r\n\r\n%s", + routeCount, routeCount == 1 ? "" : "s", + routeSummary); + + TrimNewlines(summary); + snprintf(out, outSize, "%s", summary); +} + +static void ExtractStopsSummary(const char *json, APPSTATE *state, char *out, size_t outSize) +{ + const char *stopsKey = strstr(json, "\"stops\""); + const char *p; + char summary[4096]; + int count = 0; + + summary[0] = '\0'; + if (!stopsKey || !(p = strchr(stopsKey, '['))) { + snprintf(out, outSize, "No stop details were returned for this route."); + return; + } + + p++; + while (*p && *p != ']') { + char *start = strchr((char *)p, '{'); + char *end; + char item[1024]; + char order[32]; + char name[128]; + char eta[32]; + + if (!start || !(end = FindObjectEnd(start))) { + break; + } + if ((size_t)(end - start + 1) >= sizeof(item)) { + break; + } + + memcpy(item, start, end - start + 1); + item[end - start + 1] = '\0'; + ExtractJsonFieldString(item, "order", order, sizeof(order)); + ExtractJsonFieldString(item, "name", name, sizeof(name)); + ExtractJsonFieldString(item, "estimatedArrivalMinutes", eta, sizeof(eta)); + if (name[0] == '\0') { + snprintf(name, sizeof(name), "Unnamed stop"); + } + if (eta[0] == '\0') { + snprintf(eta, sizeof(eta), "-"); + } + + snprintf(summary + strlen(summary), sizeof(summary) - strlen(summary), + "%s. %s (arrival +%s min)\r\n", order, name, eta); + if (count < 64) { + snprintf(state->stopNames[count], sizeof(state->stopNames[count]), "%s", name); + snprintf(state->stopTimes[count], sizeof(state->stopTimes[count]), "%s", eta); + } + count++; + p = end + 1; + while (*p == ' ' || *p == '\r' || *p == '\n' || *p == '\t' || *p == ',') { + p++; + } + } + + if (count == 0) { + snprintf(out, outSize, "No stop details were returned for this route."); + return; + } + + state->stopCount = count < 64 ? count : 64; + state->activeStop = 0; + snprintf(out, outSize, "Stop-by-stop timetable (%d stops)\r\n\r\n%s", count, summary); +} + +static void ExtractTokenFromJson(const char *json, char *outToken, size_t outSize) +{ + const char *tokenKey = strstr(json, "\"token\""); + const char *q1; + const char *q2; + + outToken[0] = '\0'; + if (!tokenKey) { + return; + } + + q1 = strchr(tokenKey, ':'); + if (!q1) { + return; + } + q1 = strchr(q1 + 1, '"'); + if (!q1) { + return; + } + q2 = strchr(q1 + 1, '"'); + if (!q2) { + return; + } + + q1++; + if ((size_t)(q2 - q1) >= outSize) { + strncpy(outToken, q1, outSize - 1); + outToken[outSize - 1] = '\0'; + return; + } + + memcpy(outToken, q1, q2 - q1); + outToken[q2 - q1] = '\0'; +} + +static void HttpErrorMessage(const char *context, DWORD error) +{ + (void)context; + (void)error; +} + +static int HttpRequestText(const char *method, + const char *url, + const char *body, + const char *authHeader, + char *response, + size_t responseSize) +{ + HINTERNET hSession = NULL; + HINTERNET hConnect = NULL; + HINTERNET hRequest = NULL; + wchar_t hostW[256]; + wchar_t pathW[512]; + wchar_t methodW[32]; + char host[256]; + char path[512]; + const char *src; + const char *slash; + const char *colon; + int port = 80; + DWORD flags = 0; + DWORD status = 0; + DWORD statusLen = sizeof(status); + DWORD bytesRead = 0; + DWORD totalRead = 0; + char buffer[MAX_BUF]; + int ok = 0; + + response[0] = '\0'; + + src = url; + if (strncmp(src, "http://", 7) == 0) { + src += 7; + } else if (strncmp(src, "https://", 8) == 0) { + src += 8; + } + + host[0] = '\0'; + path[0] = '/'; + path[1] = '\0'; + + slash = strchr(src, '/'); + colon = strchr(src, ':'); + + if (slash) { + size_t hostLen = (size_t)(slash - src); + if (hostLen >= sizeof(host)) { + hostLen = sizeof(host) - 1; + } + memcpy(host, src, hostLen); + host[hostLen] = '\0'; + snprintf(path, sizeof(path), "%.*s", (int)(strlen(slash)), slash); + } else if (colon) { + size_t hostLen = (size_t)(colon - src); + if (hostLen >= sizeof(host)) { + hostLen = sizeof(host) - 1; + } + memcpy(host, src, hostLen); + host[hostLen] = '\0'; + port = atoi(colon + 1); + snprintf(path, sizeof(path), "/"); + } else { + snprintf(host, sizeof(host), "%s", src); + snprintf(path, sizeof(path), "/"); + } + + if (colon && slash) { + size_t hostLen = (size_t)(colon - src); + if (hostLen >= sizeof(host)) { + hostLen = sizeof(host) - 1; + } + memcpy(host, src, hostLen); + host[hostLen] = '\0'; + port = atoi(colon + 1); + } + + if (host[0] == '\0') { + snprintf(host, sizeof(host), "192.168.68.66"); + } + + if (port == 0) { + port = 80; + } + + MultiByteToWideChar(CP_UTF8, 0, host, -1, hostW, 256); + MultiByteToWideChar(CP_UTF8, 0, path, -1, pathW, 512); + MultiByteToWideChar(CP_UTF8, 0, method, -1, methodW, 32); + + hSession = WinHttpOpen(L"TransitDriverXP/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!hSession) { + HttpErrorMessage("Unable to open WinHTTP session", GetLastError()); + return 0; + } + + hConnect = WinHttpConnect(hSession, hostW, port, 0); + if (!hConnect) { + char hostText[256]; + char errorText[512]; + WideCharToMultiByte(CP_UTF8, 0, hostW, -1, hostText, sizeof(hostText), NULL, NULL); + snprintf(errorText, sizeof(errorText), "Unable to connect to %s:%d", hostText, port); + HttpErrorMessage(errorText, GetLastError()); + goto cleanup; + } + + hRequest = WinHttpOpenRequest(hConnect, + methodW, + pathW, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + 0); + if (!hRequest) { + HttpErrorMessage("Unable to open request", GetLastError()); + goto cleanup; + } + + if (body != NULL && body[0] != '\0') { + if (!WinHttpAddRequestHeaders(hRequest, + L"Content-Type: application/json\r\n", + -1, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + HttpErrorMessage("Unable to add JSON content type", GetLastError()); + goto cleanup; + } + } + + if (authHeader && authHeader[0] != '\0') { + wchar_t wHeader[512]; + MultiByteToWideChar(CP_UTF8, 0, authHeader, -1, wHeader, 512); + if (!WinHttpAddRequestHeaders(hRequest, + wHeader, + -1, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + HttpErrorMessage("Unable to add authorization header", GetLastError()); + goto cleanup; + } + } + + if (!WinHttpSendRequest(hRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + (LPVOID)body, + body ? (DWORD)strlen(body) : 0, + body ? (DWORD)strlen(body) : 0, + 0)) { + HttpErrorMessage("WinHTTP send request failed", GetLastError()); + goto cleanup; + } + + if (!WinHttpReceiveResponse(hRequest, NULL)) { + HttpErrorMessage("WinHTTP receive response failed", GetLastError()); + goto cleanup; + } + + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, + &statusLen, + WINHTTP_NO_HEADER_INDEX)) { + HttpErrorMessage("Unable to query HTTP status", GetLastError()); + goto cleanup; + } + + if (status >= 400) { + snprintf(response, responseSize, "HTTP error %lu", status); + ok = 0; + goto cleanup; + } + + while (WinHttpQueryDataAvailable(hRequest, &bytesRead) && bytesRead > 0) { + DWORD actual = 0; + if (!WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &actual)) { + HttpErrorMessage("Unable to read response data", GetLastError()); + goto cleanup; + } + if (actual == 0) { + break; + } + buffer[actual] = '\0'; + if (totalRead + actual + 1 < responseSize) { + memcpy(response + totalRead, buffer, actual); + totalRead += actual; + response[totalRead] = '\0'; + } + } + + ok = 1; + +cleanup: + if (hRequest) { + WinHttpCloseHandle(hRequest); + } + if (hConnect) { + WinHttpCloseHandle(hConnect); + } + if (hSession) { + WinHttpCloseHandle(hSession); + } + + return ok; +} + +static void PerformLogin(HWND hwnd, APPSTATE *state) +{ + char userCode[128]; + char password[128]; + char payload[512]; + char response[MAX_BUF]; + char token[256]; + + GetWindowTextA(GetDlgItem(hwnd, ID_USERNAME), userCode, sizeof(userCode)); + GetWindowTextA(GetDlgItem(hwnd, ID_PASSWORD), password, sizeof(password)); + + if (userCode[0] == '\0' || password[0] == '\0') { + SetStatusText(hwnd, "Enter user code and password."); + return; + } + + snprintf(payload, sizeof(payload), + "{\"userCode\":\"%s\",\"password\":\"%s\"}", + userCode, password); + + SetStatusText(hwnd, "Logging in..."); + 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; + } + + ExtractTokenFromJson(response, token, sizeof(token)); + if (token[0] == '\0') { + SetOutputText(hwnd, "Driver service unreachable. Please try again."); + SetStatusText(hwnd, "Service unavailable"); + return; + } + + strncpy(state->token, token, sizeof(state->token) - 1); + state->token[sizeof(state->token) - 1] = '\0'; + ShowRouteScreen(hwnd); + SetStatusText(hwnd, "Loading today's assigned routes..."); + PerformRefresh(hwnd, state); +} + +static void PerformRefresh(HWND hwnd, APPSTATE *state) +{ + char response[MAX_BUF]; + char authHeader[512]; + char date[32]; + char scheduleUrl[256]; + + if (state->token[0] == '\0') { + SetStatusText(hwnd, "Login first."); + return; + } + + snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s\r\n", state->token); + GetWindowTextA(GetDlgItem(hwnd, ID_DATE), date, sizeof(date)); + if (date[0] == '\0') { + snprintf(date, sizeof(date), "2026-09-17"); + } + 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))) { + SetOutputText(hwnd, "Driver service unreachable. Please try again."); + SetStatusText(hwnd, "Schedule load failed"); + return; + } + + ExtractScheduleSummary(response, state, state->lastText, sizeof(state->lastText)); + SetOutputText(hwnd, state->lastText); + SetStatusText(hwnd, "Schedule loaded"); +} + +static void PerformLoadStops(HWND hwnd, APPSTATE *state) +{ + char response[MAX_BUF]; + char authHeader[512]; + char stopsUrl[256]; + LRESULT selected; + int lineId; + + if (state->token[0] == '\0') { + SetStatusText(hwnd, "Login first."); + return; + } + + selected = SendMessage(GetDlgItem(hwnd, ID_ROUTES), LB_GETCURSEL, 0, 0); + if (selected == LB_ERR || selected >= state->routeCount) { + SetStatusText(hwnd, "Select a route first."); + return; + } + + lineId = state->routeLineIds[selected]; + if (lineId <= 0) { + SetOutputText(hwnd, "This schedule does not contain a valid bus line ID."); + SetStatusText(hwnd, "Stop details unavailable"); + return; + } + + snprintf(authHeader, sizeof(authHeader), "Authorization: Bearer %s\r\n", state->token); + 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))) { + SetOutputText(hwnd, "Driver service unreachable. Please try again."); + SetStatusText(hwnd, "Stop details failed"); + return; + } + + ExtractStopsSummary(response, state, state->lastText, sizeof(state->lastText)); + ShowStartScreen(hwnd, state); + UpdateHaltButton(hwnd, state); +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static APPSTATE *state = NULL; + HWND hUser, hPassword, hLogin, hRefresh, hOutput, hStatus; + + switch (msg) { + case WM_CREATE: + { + LPCREATESTRUCT cs = (LPCREATESTRUCT)lParam; + SYSTEMTIME now; + char today[16]; + state = (APPSTATE *)calloc(1, sizeof(APPSTATE)); + state->hwnd = hwnd; + cs->lpCreateParams = state; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)state); + GetLocalTime(&now); + snprintf(today, sizeof(today), "%04d-%02d-%02d", now.wYear, now.wMonth, now.wDay); + + CreateWindowExA(0, "STATIC", "DRIVER LOGIN", + WS_CHILD | WS_VISIBLE, + 20, 10, 330, 24, + hwnd, (HMENU)ID_PAGE_TITLE, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "STATIC", "Schedule date (YYYY-MM-DD)", + WS_CHILD | WS_VISIBLE, + 20, 38, 150, 20, + hwnd, (HMENU)ID_DATE_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "EDIT", today, + WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, + 175, 36, 130, 24, + hwnd, (HMENU)ID_DATE, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "STATIC", "User code", + WS_CHILD | WS_VISIBLE, + 20, 72, 90, 20, + hwnd, (HMENU)ID_USER_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + hUser = CreateWindowExA(0, "EDIT", "", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, + 120, 70, 180, 24, + hwnd, (HMENU)ID_USERNAME, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + SetFocus(hUser); + + CreateWindowExA(0, "STATIC", "Password", + WS_CHILD | WS_VISIBLE, + 20, 104, 90, 20, + hwnd, (HMENU)ID_PASSWORD_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + hPassword = CreateWindowExA(0, "EDIT", "", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_PASSWORD | ES_AUTOHSCROLL, + 120, 102, 180, 24, + hwnd, (HMENU)ID_PASSWORD, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + hLogin = CreateWindowExA(0, "BUTTON", "Login", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 120, 138, 90, 30, + hwnd, (HMENU)ID_LOGIN, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + hRefresh = CreateWindowExA(0, "BUTTON", "Refresh schedule", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 220, 138, 130, 30, + hwnd, (HMENU)ID_REFRESH, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + hStatus = CreateWindowExA(0, "STATIC", "Idle", + WS_CHILD | WS_VISIBLE, + 20, 178, 500, 22, + hwnd, (HMENU)ID_STATUS, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "STATIC", "TODAY'S ROUTES", + WS_CHILD | WS_VISIBLE, + 20, 208, 200, 20, + hwnd, (HMENU)ID_ROUTES_LABEL, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | LBS_NOTIFY | WS_VSCROLL, + 20, 230, 500, 105, + hwnd, (HMENU)ID_ROUTES, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + hOutput = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_READONLY, + 20, 345, 500, 190, + hwnd, (HMENU)ID_OUTPUT, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "BUTTON", "Show selected route stops", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 20, 545, 190, 30, + hwnd, (HMENU)ID_STOPS, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "BUTTON", "HALT", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW, + 145, 120, 250, 250, + hwnd, (HMENU)ID_HALT, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + CreateWindowExA(0, "BUTTON", "Back to today's routes", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + 20, 545, 190, 30, + hwnd, (HMENU)ID_BACK, ((LPCREATESTRUCT)lParam)->hInstance, NULL); + + SetWindowTextA(hOutput, "Driver console ready.\r\nLog in, choose a date, and refresh the schedule."); + ShowLoginScreen(hwnd); + return 0; + } + + case WM_COMMAND: + if (LOWORD(wParam) == ID_LOGIN && HIWORD(wParam) == BN_CLICKED) { + state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + PerformLogin(hwnd, state); + return 0; + } + + if (LOWORD(wParam) == ID_REFRESH && HIWORD(wParam) == BN_CLICKED) { + state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + PerformRefresh(hwnd, state); + return 0; + } + + if (LOWORD(wParam) == ID_STOPS && HIWORD(wParam) == BN_CLICKED) { + state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + PerformLoadStops(hwnd, state); + return 0; + } + + if (LOWORD(wParam) == ID_BACK && HIWORD(wParam) == BN_CLICKED) { + state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + ShowRouteScreen(hwnd); + SetStatusText(hwnd, "Today's routes"); + return 0; + } + + if (LOWORD(wParam) == ID_HALT && HIWORD(wParam) == BN_CLICKED) { + state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->activeStop < state->stopCount) { + state->activeStop++; + UpdateHaltButton(hwnd, state); + } + return 0; + } + + if (LOWORD(wParam) == ID_ROUTES && HIWORD(wParam) == LBN_SELCHANGE) { + state = (APPSTATE *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + PerformLoadStops(hwnd, state); + return 0; + } + break; + + case WM_DRAWITEM: + if (wParam == ID_HALT) { + DrawHaltButton((DRAWITEMSTRUCT *)lParam); + return TRUE; + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + WNDCLASSA wc; + HWND hwnd; + MSG msg; + + memset(&wc, 0, sizeof(wc)); + wc.lpfnWndProc = WndProc; + wc.hInstance = hInstance; + wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); + wc.lpszClassName = "TransitDriverXPClass"; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.style = CS_HREDRAW | CS_VREDRAW; + + if (!RegisterClassA(&wc)) { + MessageBoxA(NULL, "Failed to register window class.", "XP Driver App", MB_ICONERROR | MB_OK); + return 1; + } + + hwnd = CreateWindowExA(0, + "TransitDriverXPClass", + "Transit Driver XP", + WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, + CW_USEDEFAULT, + CW_USEDEFAULT, + 560, + 640, + NULL, + NULL, + hInstance, + NULL); + + if (!hwnd) { + MessageBoxA(NULL, "Failed to create window.", "XP Driver App", MB_ICONERROR | MB_OK); + return 1; + } + + ShowWindow(hwnd, nCmdShow); + UpdateWindow(hwnd); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (int)msg.wParam; +} diff --git a/windows_xp_driver_ui/driver_xp.exe b/windows_xp_driver_ui/driver_xp.exe new file mode 100755 index 0000000000000000000000000000000000000000..f4331015e4075a9cd76139a90c1b81493302bb08 GIT binary patch literal 282465 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%0D!^>=P~dWAMl2P?a0V*9EG0b@Hq@X}whI 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%0ANG25in)|;vHhq`Tkzi+?8N{h#OBv|#76p5 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 &lv0i8z%}GrbtBQk4mEWMI_WBr+Yy3I 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 literal 0 HcmV?d00001 diff --git a/windows_xp_driver_ui/installer.nsi b/windows_xp_driver_ui/installer.nsi new file mode 100644 index 0000000..3dac844 --- /dev/null +++ b/windows_xp_driver_ui/installer.nsi @@ -0,0 +1,57 @@ +!include "MUI2.nsh" + +!define APPNAME "Transit Driver XP" +!define COMPANYNAME "Transit Project" +!define VERSION "2.0.0" + +Name "${APPNAME}" +OutFile "TransitDriverXPSetup.exe" +InstallDir "$PROGRAMFILES\TransitDriverXP" +InstallDirRegKey HKCU "Software\TransitDriverXP" "InstallDir" +RequestExecutionLevel admin +VIProductVersion "2.0.0.0" +VIAddVersionKey "ProductName" "${APPNAME}" +VIAddVersionKey "CompanyName" "${COMPANYNAME}" +VIAddVersionKey "FileDescription" "Native Windows XP driver client" +VIAddVersionKey "FileVersion" "${VERSION}" +VIAddVersionKey "ProductVersion" "${VERSION}" +VIAddVersionKey "LegalCopyright" "Transit Project" + +Function .onInit + ReadRegStr $0 HKCU "Software\TransitDriverXP" "InstallDir" + StrCmp $0 "" done + IfFileExists "$0\driver_xp.exe" 0 done + StrCpy $INSTDIR $0 + MessageBox MB_OK|MB_ICONINFORMATION "An existing Transit Driver XP installation was found. It will be updated." + ExecWait '"$SYSDIR\taskkill.exe" /F /IM driver_xp.exe' +done: +FunctionEnd + +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_LANGUAGE "English" + +Section "Install" + SetOutPath "$INSTDIR" + SetOverwrite on + File "driver_xp.exe" + WriteRegStr HKCU "Software\TransitDriverXP" "InstallDir" "$INSTDIR" + WriteRegStr HKCU "Software\TransitDriverXP" "DisplayName" "${APPNAME}" + WriteRegStr HKCU "Software\TransitDriverXP" "Version" "${VERSION}" + CreateDirectory "$SMPROGRAMS\TransitDriverXP" + CreateShortCut "$SMPROGRAMS\TransitDriverXP\Transit Driver XP.lnk" "$INSTDIR\driver_xp.exe" + CreateShortCut "$DESKTOP\Transit Driver XP.lnk" "$INSTDIR\driver_xp.exe" + WriteUninstaller "$INSTDIR\Uninstall.exe" + CreateShortCut "$SMPROGRAMS\TransitDriverXP\Uninstall Transit Driver XP.lnk" "$INSTDIR\Uninstall.exe" +SectionEnd + +Section "Uninstall" + Delete "$DESKTOP\Transit Driver XP.lnk" + Delete "$SMPROGRAMS\TransitDriverXP\Transit Driver XP.lnk" + Delete "$SMPROGRAMS\TransitDriverXP\Uninstall Transit Driver XP.lnk" + RMDir "$SMPROGRAMS\TransitDriverXP" + Delete "$INSTDIR\driver_xp.exe" + Delete "$INSTDIR\Uninstall.exe" + RMDir "$INSTDIR" + DeleteRegKey HKCU "Software\TransitDriverXP" +SectionEnd