const db = require("../db"); const { DEV_MOCK } = require("../lib/env"); const { isValidDateKey } = require("../lib/validate"); const { reportError } = require("../lib/report-error"); // Daily schedule repository methods (PostgreSQL-mode behavior; the original // handlers always queried the DB and only used `DEV_MOCK` to suppress the // fallbacks, so this module is used in both modes). async function listDailySchedules(req) { try { const { date } = req.query; const userId = req.user.userId; if (!date) { return { statusCode: 400, body: { error: "date query parameter required" } }; } const result = await db.query( ` SELECT r.id, r.start_time, r.end_time, r.status, bl.id as line_id, bl.line_number, bl.start_stop, bl.end_stop, bl.estimated_duration_minutes, bl.description, bt.id as bus_type_id, bt.name as bus_type, bt.seat_capacity, bt.license_plate FROM routes r JOIN bus_lines bl ON r.bus_line_id = bl.id 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], ); let routes = result.rows.map((row) => ({ id: row.id, startTime: row.start_time, endTime: row.end_time, status: row.status, busLine: { id: row.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 no explicit "routes" rows exist for this user/date, fall back to // checking scheduled runs in `schedules` (filtered by driver_id). // This works around cases where DB permissions prevent inserting into // `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, s.end_time, s.status, s.bus_line_id, bl.line_number, bl.start_stop, bl.end_stop, bl.estimated_duration_minutes, bl.description, s.bus_id, bt.id as bus_type_id, bt.name as bus_type, bt.seat_capacity, bt.license_plate FROM schedules s JOIN bus_lines bl ON s.bus_line_id = bl.id 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], ); 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( ` 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 schedules s JOIN bus_lines bl ON s.bus_line_id = bl.id WHERE s.driver_id = $1 ORDER BY s.start_time DESC LIMIT 1 `, [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); // 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, }, }); } } return { statusCode: 200, body: { date, routes } }; } catch (err) { console.error(err); reportError({ partofservice: `${req.method} ${req.originalUrl}`, error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error", details: err.message } }; } } async function listDriverDailySchedules(req) { try { const { date } = req.query; if (!isValidDateKey(date)) { return { statusCode: 400, body: { error: "date must use YYYY-MM-DD format" } }; } 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 schedules s JOIN bus_lines bl ON bl.id = s.bus_line_id WHERE EXISTS ( SELECT 1 FROM shifts sh WHERE sh.schedule_id = s.id AND sh.driver_id = $1 ) AND s.day_of_week = EXTRACT(DOW FROM $2::date)::int AND s.status <> 'cancelled' ORDER BY s.start_time ASC `, [req.user.userId, date], ); return { statusCode: 200, body: { 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); reportError({ partofservice: "GET /driver/schedules/daily", error: err.stack || err.message }); return { statusCode: 500, body: { error: "internal_error" } }; } } module.exports = { listDailySchedules, listDriverDailySchedules };