diff --git a/src/controllers/DelaysController.js b/src/controllers/DelaysController.js index dfe4b831..fd6ea2fe 100644 --- a/src/controllers/DelaysController.js +++ b/src/controllers/DelaysController.js @@ -6,7 +6,10 @@ const router = express.Router(); router.get("/alerts", async (req, res) => { try { const alerts = await AlertsUtils.getAlertsData(); - res.status(200).json(alerts); + res.status(200).json({ + success: true, + data: alerts, + }); } catch (error) { console.error("Error fetching alerts:", error.message); res.status(500).json({ error: "Failed to fetch alerts" }); @@ -32,7 +35,11 @@ router.post("/delays", async (req, res) => { }) ); - res.status(200).json(delays); + res.status(200).json({ + success: true, + data: allStops, + }); + } catch (error) { res.status(500).json({ error: error.message }); } diff --git a/src/controllers/RouteController.js b/src/controllers/RouteController.js index 6c294e74..2b5b1249 100644 --- a/src/controllers/RouteController.js +++ b/src/controllers/RouteController.js @@ -54,7 +54,10 @@ router.post('/route', async (req, res) => { AnalyticsUtils.assignRouteIdsAndCache(routes); // Send the sectioned routes as the response - res.json(sectionedRoutes); + res.status(200).json({ + success: true, + data: sectionedRoutes, + }); } catch (error) { LogUtils.logErr(error, req.body, 'Error processing route request'); res.status(500).json({ error: 'Failed to process the route request' }); diff --git a/src/controllers/RouteReportingController.js b/src/controllers/RouteReportingController.js index cf943369..ce9a0839 100644 --- a/src/controllers/RouteReportingController.js +++ b/src/controllers/RouteReportingController.js @@ -12,7 +12,10 @@ router.get('/closestBus', async (req, res) => { } = req.body; const closestBus = await RouteReportingUtils.getClosestBus(routeId, start); - res.status(200).json(closestBus); + res.status(200).json({ + success: true, + data: closestBus, + }); } catch (error) { LogUtils.logErr(error, req.body, 'Error fetching closest bus'); res.status(500).json({ error: 'Failed to fetch closest bus' }); @@ -29,7 +32,10 @@ router.post('/reports', async (req, res) => { } = req.body; const report = await RouteReportingUtils.insertReport(vehicleId, congestionLevel, deviceToken, timestamp); - res.status(200).json(report); + res.status(200).json({ + success: true, + data: report, + }); } catch (error) { LogUtils.logErr(error, req.body, 'Error inserting report'); res.status(500).json({ error: 'Failed to insert report' }); @@ -40,7 +46,10 @@ router.get('/reports/:vehicleId', async (req, res) => { try { const { vehicleId } = req.params; const reports = await RouteReportingUtils.fetchReportsByBus(vehicleId); - res.status(200).json(reports); + res.status(200).json({ + success: true, + data: reports, + }); } catch (error) { LogUtils.logErr(error, req.params, 'Error fetching reports by vehicleId'); res.status(500).json({ error: 'Failed to fetch reports by vehicleId' }); diff --git a/src/controllers/StopsController.js b/src/controllers/StopsController.js index a92921ef..ce22e332 100644 --- a/src/controllers/StopsController.js +++ b/src/controllers/StopsController.js @@ -7,7 +7,10 @@ const router = express.Router(); router.get("/allStops", async (req, res) => { try { const allStops = await AllStopUtils.getAllStops(); - res.status(200).json(allStops); + res.status(200).json({ + success: true, + data: allStops, + }); } catch (error) { console.error("Error fetching all stops:", error.message); res.status(500).json({ error: "Failed to fetch all stops" }); diff --git a/src/controllers/TrackingController.js b/src/controllers/TrackingController.js index e2480dda..fc009836 100644 --- a/src/controllers/TrackingController.js +++ b/src/controllers/TrackingController.js @@ -16,8 +16,11 @@ router.post("/tracking", async (req, res) => { const trackingResponse = await RealtimeFeedUtilsV3.getTrackingResponse( data ); - - res.status(200).json(trackingResponse); + + res.status(200).json({ + success: true, + data: trackingResponse, + }); } catch (error) { res.status(500).json({ error: error.message }); } diff --git a/src/data/notifRequests.json b/src/data/notifRequests.json index 9e26dfee..db770552 100644 --- a/src/data/notifRequests.json +++ b/src/data/notifRequests.json @@ -1 +1,57 @@ -{} \ No newline at end of file +{ + "t776-bFA1-slD": { + "1512": [ + "d5yOxYOS8UakmiJWNFX2Nr:APA91bGb6PSrXFd4vmD7XQN3vALrZEwTWXf2z9HeEMOXrYtNj0psn5qO_-zv0VLsPpuZYdFMXi3YSkJ2FTnJV75Fc7LNw01GGJTFzf9CWf-W-f6r9c7oKsXEVfqjz8k-BkXnTTqQhG2d" + ] + }, + "t854-b1F57-slC": { + "1328": [ + "diiwsV-LXkL-vFTrMLczKh:APA91bFVvviZuN0JvnDgawDbae2RYr2-FX3yjQrf0khhB7_7d1mhODigsCC8bZrdmdFM-Wx8yeVnSOJZnS9UACYm4VNGB0bMT_2LzYuIolKSD9xPn240pu4" + ] + }, + "t840-b1F59-slC": { + "1328": [ + "diiwsV-LXkL-vFTrMLczKh:APA91bFVvviZuN0JvnDgawDbae2RYr2-FX3yjQrf0khhB7_7d1mhODigsCC8bZrdmdFM-Wx8yeVnSOJZnS9UACYm4VNGB0bMT_2LzYuIolKSD9xPn240pu4" + ] + }, + "t898-b1F57-slC": { + "1533": [ + "diiwsV-LXkL-vFTrMLczKh:APA91bFVvviZuN0JvnDgawDbae2RYr2-FX3yjQrf0khhB7_7d1mhODigsCC8bZrdmdFM-Wx8yeVnSOJZnS9UACYm4VNGB0bMT_2LzYuIolKSD9xPn240pu4" + ] + }, + "t868-b1F59-slC": { + "1534": [ + "diiwsV-LXkL-vFTrMLczKh:APA91bFVvviZuN0JvnDgawDbae2RYr2-FX3yjQrf0khhB7_7d1mhODigsCC8bZrdmdFM-Wx8yeVnSOJZnS9UACYm4VNGB0bMT_2LzYuIolKSD9xPn240pu4" + ] + }, + "t8CF-b1F4A-slC": { + "1343": [ + "de-EtLrD6Ub1sEK8dSm1jr:APA91bEU9KIQElzFZefk6KRhQ0ADcVflRp3MHhWrLBZa8DTIHVLac1wQTVYfZ5wTRfKOWGpxFFnKWyTunYJKLOC54bP3n5YPyCyHhje7skYx4y673pJXcbI" + ] + }, + "t908-b1F59-slC": {}, + "t91C-b1F57-slC": {}, + "t930-b1F59-slC": {}, + "t960-b1F4B-slC": {}, + "tC-b1F57-slC": {}, + "t20-b1F59-slC": {}, + "t13-b1F4B-slC": {}, + "t14-b1F59-slC": {}, + "t28-b1F57-slC": {}, + "t31-b1F4B-slC": { + "1531": [ + "de-EtLrD6Ub1sEK8dSm1jr:APA91bEU9KIQElzFZefk6KRhQ0ADcVflRp3MHhWrLBZa8DTIHVLac1wQTVYfZ5wTRfKOWGpxFFnKWyTunYJKLOC54bP3n5YPyCyHhje7skYx4y673pJXcbI" + ] + }, + "t7D0-b1F4B-slD": {}, + "t7DC-b1F57-slD": {}, + "t7DA-b1F5B-slD": {}, + "t804-b1F57-slD": {}, + "t7DF-b1F44-slD": {}, + "t898-b1F4B-slD": {}, + "t86D-b1F44-slD": {}, + "t8A4-b1F57-slD": {}, + "t898-b1F57-slD": {}, + "t8C0-b1F57-slD": {}, + "t8C9-b1F4B-slD": {} +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index efef3dc0..db318353 100644 --- a/src/index.js +++ b/src/index.js @@ -16,32 +16,35 @@ import RealtimeFeedUtilsV3 from "./utils/RealtimeFeedUtilsV3.js"; import admin from "firebase-admin"; import swaggerUi from "swagger-ui-express"; -import swaggerDoc from "./swagger.json" with { type: "json" }; +import fs from "fs"; +const swaggerDoc = JSON.parse( + fs.readFileSync(new URL("./swagger.json", import.meta.url)) +); + import AlertsUtils from "./utils/AlertsUtils.js"; import AllStopUtils from "./utils/AllStopUtils.js"; import GTFSUtils from "./utils/GTFSUtils.js"; - const app = express(); const port = process.env.PORT; app.use(express.json()); -app.use('/api/v1/', delayRoutes); +app.use("/api/v1/", delayRoutes); -app.use('/api/v3/', routeRoutes); +app.use("/api/v3/", routeRoutes); -app.use('/api/v3/', trackingRoutes); +app.use("/api/v3/", trackingRoutes); -app.use('/api/v2/', searchRoutes); +app.use("/api/v3/", searchRoutes); -app.use('/api/v1/', stopsRoutes); +app.use("/api/v1/", stopsRoutes); -app.use('/api/v1/', notifRoutes); +app.use("/api/v1/", notifRoutes); -app.use('/api/v1/', reportingRoutes); +app.use("/api/v1/", reportingRoutes); -app.use('/api/v1/', ecosystemRoutes); +app.use("/api/v1/", ecosystemRoutes); // Setup Swagger docs app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDoc)); diff --git a/src/utils/NotificationUtils.js b/src/utils/NotificationUtils.js index 6186ca80..80479ae3 100644 --- a/src/utils/NotificationUtils.js +++ b/src/utils/NotificationUtils.js @@ -4,6 +4,7 @@ import { fileURLToPath } from "url"; import fs from "fs"; import path from "path"; import RealtimeFeedUtilsV3 from "./RealtimeFeedUtilsV3.js"; +import LogUtils from "./LogUtils.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -59,54 +60,86 @@ function deleteDelayNotification(tripID, stopID, deviceToken) { function sendNotifications() { const rtfData = RealtimeFeedUtilsV3.getRTFData(); - + if (!rtfData) { - // no real-time data available yet return; } - + + const tokensToDelete = []; + for (const id in rtfData) { if (id in notifRequests) { for (const stopID in notifRequests[id]) { if (stopID in rtfData[id]["stopUpdates"]) { - for (const deviceToken of notifRequests[id][stopID]) { - sendNotification( - deviceToken, - `The bus on ${rtfData[id]["routeId"]} is delayed`, - "testBody" - ); + //only send a notification if there is a delay + if (rtfData[id]["stopUpdates"][stopID] > 0) { + for (const deviceToken of notifRequests[id][stopID]) { + const notifData = { + title: "Delay Notification", + body: `The bus on ${rtfData[id]["routeId"]} is delayed`, + }; + + sendNotification(deviceToken, notifData); + + tokensToDelete.push({ id, stopID, deviceToken }); + } } } } } } + + for (const { id, stopID, deviceToken } of tokensToDelete) { + if ( + notifRequests[id] && + notifRequests[id][stopID] && + Array.isArray(notifRequests[id][stopID]) + ) { + notifRequests[id][stopID] = notifRequests[id][stopID].filter( + (token) => token !== deviceToken + ); + if (notifRequests[id][stopID].length === 0) { + delete notifRequests[id][stopID]; + } + } + } + saveNotifs(); } -function sendNotification(deviceToken, data, notification) { +async function sendNotification(deviceToken, notif) { const message = { - data: { - data, - notification, - }, token: deviceToken, + notification: { + title: notif.title, + body: notif.body, + }, }; + if (!message.token) { throw new Error("Invalid device token"); } - getMessaging() - .send(message) - .then((response) => { - console.log(response); - }); + + try { + const response = await getMessaging() + .send(message) + .then((response) => { + LogUtils.log({ message: response }); + console.log(response); + }); + + console.log("Notification sent successfully:", response); + } catch (error) { + console.error("Error sending notification:", error.code, error.message); + } } function waitForDeparture(deviceToken, startTime) { const startDate = new Date(parseInt(startTime) * 1000 - 60000 * 10); const notifData = { - data: "You should board your bus in 10 minutes", - notification: "Bording Reminder", + body: "You should board your bus in 10 minutes", + title: "Bording Reminder", }; const job = schedule.scheduleJob(startDate, () => { @@ -121,13 +154,14 @@ function waitForDeparture(deviceToken, startTime) { } function cancelDeparture(deviceToken, startTime) { - const startDate = new Date(parseInt(startTime) * 1000 - 60000 * 10); + let startDate = new Date(parseInt(startTime) * 1000 - 60000 * 10); + startDate = startDate.toString(); if (deviceToken in departures) { if (startDate in departures[deviceToken]) { if (departures[deviceToken][startDate]) { departures[deviceToken][startDate].cancel(); - console.log("Job canceled."); + LogUtils.log({ message: "job canceled" }); } } } diff --git a/src/utils/RealtimeFeedUtilsV3.js b/src/utils/RealtimeFeedUtilsV3.js index 65d75229..a95e088b 100644 --- a/src/utils/RealtimeFeedUtilsV3.js +++ b/src/utils/RealtimeFeedUtilsV3.js @@ -146,7 +146,6 @@ function getVehicleData() { * */ async function getTrackingResponse(requestData) { - LogUtils.log({ message: "getTrackingResponse: entering function" }); const vehicles = getVehicleData(); const trackingInformation = requestData @@ -154,7 +153,6 @@ async function getTrackingResponse(requestData) { const { routeId, tripId } = data; const vehicleData = getVehicleInformation(routeId, tripId, vehicles); if (!vehicleData) { - LogUtils.log({ message: "getVehicleResponse: noData", vehicleData }); return null; } return vehicleData; @@ -203,22 +201,12 @@ function getVehicleInformation(routeId, tripId, vehicles) { // vehicles param ensures the vehicle tracking information doesn't update in // the middle of execution if (!routeId || !tripId || !vehicles) { - LogUtils.log({ - category: "getVehicleInformation NULL", - routeId, - tripId, - }); return null; } const vehicleData = Object.values(vehicles).find( (v) => v.routeId === routeId && v.tripId === tripId ); if (!vehicleData) { - LogUtils.log({ - category: "getVehicleInformation no data", - routeId, - tripId, - }); return { case: "noData", latitude: 0,