const { store } = require("../mock/store"); const { reportError } = require("../lib/report-error"); // Bus-line repository methods (DEV_MOCK mode). Each mirrors the original // handler's `if (DEV_MOCK) { ... }` branch verbatim and returns // { statusCode, body }. The original reorder/delete handlers acquired a // db.pool.connect() before the mock branch; the mock implementation below // intentionally skips that so mock-only deletion/reorder work without a // database (documented improvement). async function listLineStops(req) { try { const { busLineId } = req.params; const stops = store.routeStops .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, latitude: rs.latitude, longitude: rs.longitude, estimatedArrivalMinutes: rs.estimatedArrivalMinutes, })); return { statusCode: 200, body: { stops } }; } 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 listLines(req) { try { return { statusCode: 200, body: { busLines: store.busLines .slice() .sort((a, b) => a.lineNumber - b.lineNumber), }, }; } 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 createLine(req) { try { const { lineNumber, startStop, endStop, estimatedDurationMinutes, description, } = req.body; if ( lineNumber === undefined || !startStop || !endStop || estimatedDurationMinutes === undefined ) { return { statusCode: 400, body: { error: "missing_required_fields" } }; } const busLine = { id: store.nextBusLineId++, lineNumber: Number(lineNumber), startStop, endStop, estimatedDurationMinutes: Number(estimatedDurationMinutes), description: description || "", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; store.busLines.push(busLine); return { statusCode: 201, body: { busLine } }; } 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 updateLine(req) { try { const { busLineId } = req.params; const { lineNumber, startStop, endStop, estimatedDurationMinutes, description, } = req.body; const existing = store.busLines.find( (line) => String(line.id) === String(busLineId), ); if (!existing) return { statusCode: 404, body: { 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 { statusCode: 200, body: { busLine: existing } }; } 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 listLineStopsDetailed(req) { try { const { busLineId } = req.params; if (!busLineId || !Number.isFinite(Number(busLineId))) { return { statusCode: 400, body: { error: "invalid_bus_line_id" } }; } const stops = store.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 { statusCode: 200, body: { routeStops: stops } }; } 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 addLineStop(req) { try { const { busLineId } = req.params; const { stopId, stopOrder, estimatedArrivalMinutes } = req.body; if (!stopId || stopOrder === undefined) { return { statusCode: 400, body: { error: "missing_required_fields" } }; } const stop = store.stops.find((s) => String(s.id) === String(stopId)); const routeStop = { id: store.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 store.routeStops = store.routeStops.filter( (rs) => !( rs.busLineId === routeStop.busLineId && rs.stopOrder === routeStop.stopOrder ), ); store.routeStops.push(routeStop); return { statusCode: 201, body: { routeStop } }; } 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 reorderLineStops(req) { try { const { busLineId } = req.params; const { routeStopIds } = req.body || {}; if (!Array.isArray(routeStopIds) || routeStopIds.length === 0) { return { statusCode: 400, body: { error: "routeStopIds array required" } }; } const normalizedIds = routeStopIds .map((id) => Number(id)) .filter((id) => Number.isFinite(id)); if (normalizedIds.length !== routeStopIds.length) { return { statusCode: 400, body: { error: "routeStopIds must contain only numeric ids" } }; } const busLineStops = store.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 { statusCode: 400, body: { error: "routeStopIds must match current bus line stops" } }; } const updated = normalizedIds.map((routeStopId, index) => { const stop = store.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 { statusCode: 400, body: { error: "routeStopIds must match current bus line stops" } }; } store.routeStops = store.routeStops.map((routeStop) => { const reordered = updated.find((item) => item.id === routeStop.id); return reordered || routeStop; }); return { statusCode: 200, body: { routeStops: updated } }; } 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 deleteLine(req) { try { const { busLineId } = req.params; if (!busLineId || !Number.isFinite(Number(busLineId))) { return { statusCode: 400, body: { error: "invalid_bus_line_id" } }; } const lineIndex = store.busLines.findIndex( (bl) => Number(bl.id) === Number(busLineId), ); if (lineIndex === -1) { return { statusCode: 404, body: { error: "bus_line_not_found" } }; } const deletedLine = store.busLines.splice(lineIndex, 1)[0]; // Also delete route_stops for this line store.routeStops = store.routeStops.filter( (rs) => String(rs.busLineId) !== String(busLineId), ); return { statusCode: 200, body: { success: true, busLine: deletedLine } }; } 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 deleteLineStop(req) { try { const { busLineId, routeStopId } = req.params; if (!busLineId || !Number.isFinite(Number(busLineId))) { return { statusCode: 400, body: { error: "invalid_bus_line_id" } }; } if (!routeStopId || !Number.isFinite(Number(routeStopId))) { return { statusCode: 400, body: { error: "invalid_route_stop_id" } }; } const index = store.routeStops.findIndex( (rs) => Number(rs.id) === Number(routeStopId) && String(rs.busLineId) === String(busLineId), ); if (index === -1) { return { statusCode: 404, body: { error: "route_stop_not_found" } }; } const deletedRouteStop = store.routeStops.splice(index, 1)[0]; return { statusCode: 200, body: { success: true, routeStop: deletedRouteStop } }; } 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 } }; } } module.exports = { listLineStops, listLines, createLine, updateLine, listLineStopsDetailed, addLineStop, reorderLineStops, deleteLine, deleteLineStop, };