const async = require("async") const { ctx } = require("@modjo/core") const { taskCtx } = require("@modjo/microservice-worker/ctx") const humanizeDistance = require("utils/geo/humanizeDistance") const addNotification = require("~/services/add-notification") const { getAlertNotificationExpirationInSeconds, ALERT_NOTIFICATION_EXPIRATION_INTERVAL, } = require("~/constants/time") const { LEVEL_COLORS, LEVEL_DESCRIPTIONS, } = require("~/constants/notifications") const MAX_PARALLEL_PUSHES = 10 function createAlertNotification({ code, reason, level, alertId, alertingId, initialDistance, }) { const baseText = reason === "relative" ? "Un de vos proches a besoin d'aide" : "Besoin d'aide" const levelText = LEVEL_DESCRIPTIONS[level] || LEVEL_DESCRIPTIONS.green const distanceText = initialDistance ? ` à ${humanizeDistance(initialDistance)}` : "" const notificationText = `${baseText} - ${levelText}${distanceText}` const color = LEVEL_COLORS[level] || LEVEL_COLORS.green const expires = getAlertNotificationExpirationInSeconds() return { data: { action: "alert", alertingId, alertId, level, expires, code, initialDistance, reason, }, notification: { title: `Nouvelle Alerte - #${code}`, body: notificationText, channel: "alert", icon: `notif-${level}`, priority: "high", ttl: ALERT_NOTIFICATION_EXPIRATION_INTERVAL, color, actionId: "open-alert", }, } } const numericLevel = { green: 1, yellow: 2, red: 3, } module.exports = async function () { async function setNotificationSent(alertingId, state) { const sql = ctx.require("postgres") const logger = taskCtx.require("logger") logger.debug({ alertingId, state }, "Setting notification sent state") if (state === false) { logger.debug( { alertingId }, "Updating alerting record to mark notification_sent as FALSE" ) await sql` UPDATE "alerting" SET "notification_sent" = FALSE WHERE "id" = ${alertingId} ` } else { logger.debug( { alertingId }, "Updating alerting record to mark notification_sent as TRUE with current timestamp" ) await sql` UPDATE "alerting" SET "notification_sent" = TRUE, "notification_sent_at" = NOW() WHERE "id" = ${alertingId} ` } logger.debug({ alertingId }, "Successfully updated notification sent state") } return Object.assign( async function alertNotify(params) { const logger = taskCtx.require("logger") logger.info({ params }, "Starting alertNotify process") const sql = ctx.require("postgres") const { alertingId } = params logger.debug({ alertingId }, "Querying alerting record") const [alertingRow] = await sql` SELECT "alerting"."device_id" as "deviceId", "alerting"."alert_id" as "alertId", "alerting"."user_id" as "userId", "alerting"."reason" as "reason", "alerting"."initial_distance" as "initialDistance" FROM "alerting" WHERE "alerting"."id" = ${alertingId} AND "alerting"."notification_sent" IS NULL ` if (!alertingRow) { logger.info({ alertingId }, "No alerting record found, exiting process") return } const { reason, alertId, userId: alertingUserId } = alertingRow logger.debug({ reason, alertId, alertingUserId }, "Found alerting record") const devicesList = await sql` SELECT "id", "fcm_token" as "fcmToken", "notification_alert_level" as "notificationAlertLevel" FROM "device" WHERE "user_id" = ${alertingUserId} AND "fcm_token" IS NOT NULL ` const devices = devicesList.map((device) => ({ ...device })) logger.debug({ alertId }, "Querying alert record") const [{ userId: alertUserId, level, code }] = await sql` SELECT "alert"."level" as "level", "alert"."user_id" as "userId", "alert"."code" as "code" FROM "alert" WHERE "alert"."id" = ${alertId} ` let sentOnce = false await async.allLimit(devices, MAX_PARALLEL_PUSHES, async (device) => { const { id: deviceId, fcmToken } = device const notificationAlertLevel = device.notificationAlertLevel || "green" logger.debug( { deviceId, notificationAlertLevel }, "Found device record" ) if (alertUserId === alertingUserId) { logger.info( { alertUserId, alertingUserId }, "Alert creator matches alerting user, skipping notification" ) await setNotificationSent(alertingId, false) // user doesn't receive it's own alerts notifications // disable/comment return for dev/debug return } const acceptLevel = numericLevel[level] >= numericLevel[notificationAlertLevel] logger.debug( { level, notificationAlertLevel, acceptLevel }, "Checking alert level threshold" ) if (!acceptLevel) { logger.info( { level, notificationAlertLevel }, "Alert level below device threshold, skipping notification" ) await setNotificationSent(alertingId, false) return } try { logger.debug({ deviceId }, "Attempting to send push notification") const notificationConfig = createAlertNotification({ code, reason, level, alertId, alertingId, initialDistance: alertingRow.initialDistance, }) const { messageId, success } = await addNotification({ fcmToken, deviceId, ...notificationConfig, userId: alertingUserId, type: "alert", }) if (!success) { logger.error( { deviceId, alertingId }, "Failed to send push notification" ) return } logger.info( { messageId, alertingId }, "Successfully sent push notification" ) sentOnce = true } catch (e) { logger.error( { error: e, deviceId, alertingId }, "Failed to send push notification" ) console.error(e) } }) logger.info( { alertingId, sentOnce }, "Setting final notification sent state" ) await setNotificationSent(alertingId, sentOnce) }, { dedupOptions: { enabled: true }, } ) }