feature/IO-3492-FCM-Queue-For-Notifications: Finalize

This commit is contained in:
Dave
2026-01-05 18:42:40 -05:00
parent 4cdc15f70b
commit 00bf5977ae
5 changed files with 170 additions and 35 deletions

View File

@@ -27,6 +27,16 @@ let emailConsolidateQueue;
let emailAddWorker;
let emailConsolidateWorker;
const seconds = (ms) => Math.max(1, Math.ceil(ms / 1000));
const escapeHtml = (s = "") =>
String(s)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
/**
* Initializes the email notification queues and workers.
*
@@ -65,19 +75,21 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
const redisKeyPrefix = `email:${devKey}:notifications:${jobId}`;
for (const recipient of recipients) {
for (const recipient of recipients || []) {
const { user, firstName, lastName } = recipient;
if (!user) continue;
const userKey = `${redisKeyPrefix}:${user}`;
await pubClient.rpush(userKey, body);
await pubClient.expire(userKey, NOTIFICATION_EXPIRATION / 1000);
await pubClient.expire(userKey, seconds(NOTIFICATION_EXPIRATION));
const detailsKey = `email:${devKey}:recipientDetails:${jobId}:${user}`;
await pubClient.hsetnx(detailsKey, "firstName", firstName || "");
await pubClient.hsetnx(detailsKey, "lastName", lastName || "");
await pubClient.hsetnx(detailsKey, "bodyShopTimezone", bodyShopTimezone);
await pubClient.expire(detailsKey, NOTIFICATION_EXPIRATION / 1000);
const tzValue = bodyShopTimezone || "UTC";
await pubClient.hsetnx(detailsKey, "bodyShopTimezone", tzValue);
await pubClient.expire(detailsKey, seconds(NOTIFICATION_EXPIRATION));
const recipientsSetKey = `email:${devKey}:recipients:${jobId}`;
await pubClient.sadd(recipientsSetKey, user);
await pubClient.expire(recipientsSetKey, NOTIFICATION_EXPIRATION / 1000);
await pubClient.expire(recipientsSetKey, seconds(NOTIFICATION_EXPIRATION));
devDebugLogger(`Stored message for ${user} under ${userKey}: ${body}`);
}
@@ -95,7 +107,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
}
);
devDebugLogger(`Scheduled email consolidation for jobId ${jobId}`);
await pubClient.expire(consolidateKey, CONSOLIDATION_KEY_EXPIRATION / 1000);
await pubClient.expire(consolidateKey, seconds(CONSOLIDATION_KEY_EXPIRATION));
} else {
devDebugLogger(`Email consolidation already scheduled for jobId ${jobId}`);
}
@@ -115,7 +127,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
devDebugLogger(`Consolidating emails for jobId ${jobId}`);
const lockKey = `lock:${devKey}:emailConsolidate:${jobId}`;
const lockAcquired = await pubClient.set(lockKey, "locked", "NX", "EX", LOCK_EXPIRATION / 1000);
const lockAcquired = await pubClient.set(lockKey, "locked", "NX", "EX", seconds(LOCK_EXPIRATION));
if (lockAcquired) {
try {
const recipientsSet = `email:${devKey}:recipients:${jobId}`;
@@ -141,7 +153,7 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
<table class="row" style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; padding: 0; width: 100%; position: relative; display: table;"><tbody style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; display: table-row-group;"><tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;">
<th class="small-12 large-12 columns first last" style="word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; line-height: 1.2; margin: 0 auto; Margin: 0 auto; padding-bottom: 16px; width: 734px; padding-left: 8px; padding-right: 8px; border-collapse: collapse;"><table style="border-spacing: 0; border-collapse: collapse; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; width: 100%;"><tr style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; vertical-align: top; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif;"><td style="word-wrap: break-word; vertical-align: top; color: #0a0a0a; font-weight: normal; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin: 0; Margin: 0; text-align: left; font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 15px; word-break: keep-all; -moz-hyphens: none; -ms-hyphens: none; -webkit-hyphens: none; hyphens: none; line-height: 1.2; border-collapse: collapse;">
<ul style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; margin: 1%; padding-left: 30px;">
${messages.map((msg) => `<li style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%;">${msg}</li>`).join("")}
${messages.map((msg) => `<li style="font-family: 'Montserrat', 'Montserrat Alternates', sans-serif; font-size: 90%;">${escapeHtml(msg)}</li>`).join("")}
</ul>
</td></tr></table></th>
</tr><tbody></table>