From a21486511ca025c652c2f31093bdd09566429ae9 Mon Sep 17 00:00:00 2001 From: NoEnemies <163070914+WithoutEnemies@users.noreply.github.com> Date: Fri, 29 May 2026 16:36:20 +0100 Subject: [PATCH] fix(alerts): render map markers Preserve alert coordinates in the API response so the website can render alert icons on the map. --- apps/sync-alerts/src/services/parseAlertV2.ts | 11 ++++-- apps/sync-alerts/src/tasks/sync-alerts.ts | 38 +++++++++++-------- apps/sync-alerts/src/types/sources.ts | 12 ++++++ package.json | 22 +++++------ packages/types/src/api/alerts.ts | 2 + 5 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 apps/sync-alerts/src/types/sources.ts diff --git a/apps/sync-alerts/src/services/parseAlertV2.ts b/apps/sync-alerts/src/services/parseAlertV2.ts index f1044ab4..7165b151 100644 --- a/apps/sync-alerts/src/services/parseAlertV2.ts +++ b/apps/sync-alerts/src/services/parseAlertV2.ts @@ -5,8 +5,10 @@ import type { Alert } from '@carrismetropolitana/api-types/gtfs-core'; /* * */ export default function parseAlertV2(item): Alert { - // - const parsedInformedEntity = item.alert.informed_entity.map((entity) => { + + const alertData = item.alert ?? item; + const alertId = item.id ?? item.alert_id; + const parsedInformedEntity = alertData.informed_entity.map((entity) => { if (entity.route_id) { return { line_id: entity.route_id.substring(0, 4), @@ -24,8 +26,9 @@ export default function parseAlertV2(item): Alert { }); return { - ...item.alert, - alert_id: item.id, + ...alertData, + alert_id: alertId, + coordinates: alertData.coordinates, informed_entity: parsedInformedEntity, }; diff --git a/apps/sync-alerts/src/tasks/sync-alerts.ts b/apps/sync-alerts/src/tasks/sync-alerts.ts index 9cd3ed9e..ce5c3404 100644 --- a/apps/sync-alerts/src/tasks/sync-alerts.ts +++ b/apps/sync-alerts/src/tasks/sync-alerts.ts @@ -1,11 +1,8 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - /* * */ -import parseAlertV2 from '@/services/parseAlertV2.js'; +import { type BackofficeAlertSource } from '@/types/sources.js'; import { SERVERDB } from '@carrismetropolitana/api-services/SERVERDB'; import { SERVERDB_KEYS } from '@carrismetropolitana/api-settings'; -import { type Alert } from '@carrismetropolitana/api-types/alerts'; import LOGGER from '@helperkits/logger'; import TIMETRACKER from '@helperkits/timer'; import { ServiceAlertResponse } from '@tmlmobilidade/types'; @@ -25,14 +22,22 @@ export const syncAlerts = async () => { const backofficeTimer = new TIMETRACKER(); - const alertsFeedResponse = await fetchData('https://go.tmlmobilidade.pt/hub/api/v1/alerts/gtfs'); + const alertsGtfsFeedResponse = await fetchData('https://go.tmlmobilidade.pt/hub/api/v1/alerts/gtfs'); + + if (alertsGtfsFeedResponse.error || !alertsGtfsFeedResponse.data) { + LOGGER.error(`Failed to fetch Alerts GTFS feed from the backoffice: ${alertsGtfsFeedResponse.error}`); + return; + } + + const alertsJsonFeedResponse = await fetchData('https://go.tmlmobilidade.pt/hub/api/v1/alerts'); - if (alertsFeedResponse.error) { - LOGGER.error(`Failed to fetch Alerts feed from the backoffice: ${alertsFeedResponse.error}`); + if (alertsJsonFeedResponse.error || !alertsJsonFeedResponse.data) { + LOGGER.error(`Failed to fetch Alerts JSON feed from the backoffice: ${alertsJsonFeedResponse.error}`); return; } - const alertsFeedData: any = alertsFeedResponse.data; + const alertsGtfsFeedData = alertsGtfsFeedResponse.data; + const alertsJsonFeedData = alertsJsonFeedResponse.data; LOGGER.info(`Fetched Alerts feed from the backoffice (${backofficeTimer.get()})`); @@ -42,7 +47,7 @@ export const syncAlerts = async () => { const protobufTimer = new TIMETRACKER(); - await SERVERDB.set(SERVERDB_KEYS.NETWORK.ALERTS.PROTOBUF, JSON.stringify(alertsFeedData)); + await SERVERDB.set(SERVERDB_KEYS.NETWORK.ALERTS.PROTOBUF, JSON.stringify(alertsGtfsFeedData)); LOGGER.info(`Saved Protobuf Alerts to ServerDB (${protobufTimer.get()})`); @@ -51,7 +56,7 @@ export const syncAlerts = async () => { const jsonTimer = new TIMETRACKER(); - const allAlertsParsedV2: Alert[] = alertsFeedData?.entity.map(item => parseAlertV2(item)); + const allAlertsParsedV2 = alertsJsonFeedData; await SERVERDB.set(SERVERDB_KEYS.NETWORK.ALERTS.ALL, JSON.stringify(allAlertsParsedV2)); @@ -71,7 +76,7 @@ export const syncAlerts = async () => { let sentNotificationCounter = 0; for (const alertItem of allAlertsParsedV2) { - if (!allSentNotificationsSet.has(alertItem['_id'])) { + if (!allSentNotificationsSet.has(alertItem.alert_id)) { try { for (const entity of alertItem['informed_entity']) { // Setup notification message @@ -94,7 +99,7 @@ export const syncAlerts = async () => { topic: '', }; // Include alert id - notificationMessage.data.alertId = alertItem['_id']; + notificationMessage.data.alertId = alertItem.alert_id; // Include title if (alertItem.header_text?.translation?.length > 0) { notificationMessage.notification.title = alertItem.header_text?.translation[0]?.text ?? ''; @@ -112,6 +117,9 @@ export const syncAlerts = async () => { if (entity.route_id) { notificationMessage.topic = `cm.realtime.alerts.line.${entity.route_id}`; } + else if (entity.line_id) { + notificationMessage.topic = `cm.realtime.alerts.line.${entity.line_id}`; + } else if (entity.stop_id) { notificationMessage.topic = `cm.realtime.alerts.stop.${entity.stop_id}`; } @@ -126,11 +134,11 @@ export const syncAlerts = async () => { // await firebaseAdmin.messaging().send(notificationMessage); sentNotificationCounter++; } - allSentNotificationsSet.add(alertItem['_id']); - LOGGER.success(`Sent notification for alert: ${alertItem['_id']}`); + allSentNotificationsSet.add(alertItem.alert_id); + LOGGER.success(`Sent notification for alert: ${alertItem.alert_id}`); } catch (error) { - LOGGER.error(`Failed to send notification for alert: ${alertItem['_id']}`); + LOGGER.error(`Failed to send notification for alert: ${alertItem.alert_id}`); LOGGER.error(error); continue; } diff --git a/apps/sync-alerts/src/types/sources.ts b/apps/sync-alerts/src/types/sources.ts new file mode 100644 index 00000000..556fd5d5 --- /dev/null +++ b/apps/sync-alerts/src/types/sources.ts @@ -0,0 +1,12 @@ +/* * */ + +import { type Alert } from '@carrismetropolitana/api-types/alerts'; + +/* * */ + +export type AlertInformedEntitySource = Alert['informed_entity'][number] & { line_id?: string }; + +export type BackofficeAlertSource = Omit & { + alert_id: string + informed_entity: AlertInformedEntitySource[] +}; diff --git a/package.json b/package.json index 249660f1..b4234af6 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "scripts": { "build": "turbo run build", "build:packages": "turbo run build --filter=./packages/*", - "dev": "dotenv-run -f ./environments/development/secrets/.env -- turbo run dev --concurrency=99", - "dev:server": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-server", - "dev:switch": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-switch", - "dev:sync-alert-metrics": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-alert-metrics", - "dev:sync-alerts": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-alerts", - "dev:sync-facilities": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-facilities", - "dev:sync-gtfs": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-gtfs", - "dev:sync-locations": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-locations", - "dev:sync-metrics": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-metrics", - "dev:sync-stores": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-stores", - "dev:sync-vehicles": "dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-vehicles", + "dev": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo run dev --concurrency=99", + "dev:server": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-server", + "dev:switch": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-switch", + "dev:sync-alert-metrics": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-alert-metrics", + "dev:sync-alerts": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-alerts", + "dev:sync-facilities": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-facilities", + "dev:sync-gtfs": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-gtfs", + "dev:sync-locations": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-locations", + "dev:sync-metrics": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-metrics", + "dev:sync-stores": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-stores", + "dev:sync-vehicles": "NODE_ENV=development dotenv-run -f ./environments/development/secrets/.env -- turbo dev --filter=@carrismetropolitana/api-sync-vehicles", "lint": "turbo run lint", "rinse": "npx --yes @tmlmobilidade/repo-rinse" }, diff --git a/packages/types/src/api/alerts.ts b/packages/types/src/api/alerts.ts index 4660ad22..958c150b 100644 --- a/packages/types/src/api/alerts.ts +++ b/packages/types/src/api/alerts.ts @@ -31,10 +31,12 @@ interface LocalizedImage { /** * An Alert is the JSON equivalent of a GTFS-RT Service Alert message. * Please use a SimplifiedAlert as many convenience operations are already correctly applied. + * Coordinates are optional and may not be present in all alerts. They are provided as a [latitude, longitude] pair. */ export interface Alert { active_period: TimeRange[] cause: Cause + coordinates?: [number, number] description_text: TranslatedString effect: Effect header_text: TranslatedString