feature/IO-3096-GlobalNotifications - Checkpoint, fixed some email bugs in other files, consolidated the GetEndpoints on the backend, moved the consolidation delays for queues to ENV vars

This commit is contained in:
Dave Richer
2025-02-20 13:13:09 -05:00
parent cc5fea9410
commit c82cfb3ec2
7 changed files with 85 additions and 83 deletions

View File

@@ -7,7 +7,7 @@ const OAuthClient = require("intuit-oauth");
const client = require("../../graphql-client/graphql-client").client; const client = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries"); const queries = require("../../graphql-client/queries");
const { parse, stringify } = require("querystring"); const { parse, stringify } = require("querystring");
const InstanceManager = require("../../utils/instanceMgr").default; const { InstanceEndpoints } = require("../../utils/instanceMgr");
const oauthClient = new OAuthClient({ const oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID, clientId: process.env.QBO_CLIENT_ID,
@@ -17,16 +17,8 @@ const oauthClient = new OAuthClient({
logging: true logging: true
}); });
let url; //TODO:AIO Add in QBO callbacks.
const url = InstanceEndpoints();
if (process.env.NODE_ENV === "production") {
//TODO:AIO Add in QBO callbacks.
url = InstanceManager({ imex: `https://imex.online`, rome: `https://romeonline.io` });
} else if (process.env.NODE_ENV === "test") {
url = InstanceManager({ imex: `https://test.imex.online`, rome: `https://test.romeonline.io` });
} else {
url = `http://localhost:3000`;
}
exports.default = async (req, res) => { exports.default = async (req, res) => {
const queryString = req.url.split("?").reverse()[0]; const queryString = req.url.split("?").reverse()[0];

View File

@@ -239,24 +239,24 @@ const emailBounce = async (req, res) => {
return; return;
} }
//If it's bounced, log it as bounced in audit log. Send an email to the user. //If it's bounced, log it as bounced in audit log. Send an email to the user.
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, { await client.request(queries.UPDATE_EMAIL_AUDIT, {
sesid: messageId, sesid: messageId,
status: "Bounced", status: "Bounced",
context: message.bounce?.bouncedRecipients context: message.bounce?.bouncedRecipients
}); });
mailer.sendMail( mailer.sendMail(
{ {
from: InstanceMgr({ from: InstanceManager({
imex: `ImEX Online <noreply@imex.online>`, imex: `ImEX Online <noreply@imex.online>`,
rome: `Rome Online <noreply@romeonline.io>` rome: `Rome Online <noreply@romeonline.io>`
}), }),
to: replyTo, to: replyTo,
//bcc: "patrick@snapt.ca", //bcc: "patrick@snapt.ca",
subject: `${InstanceMgr({ subject: `${InstanceManager({
imex: "ImEX Online", imex: "ImEX Online",
rome: "Rome Online" rome: "Rome Online"
})} Bounced Email - RE: ${subject}`, })} Bounced Email - RE: ${subject}`,
text: `${InstanceMgr({ text: `${InstanceManager({
imex: "ImEX Online", imex: "ImEX Online",
rome: "Rome Online" rome: "Rome Online"
})} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error. })} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
@@ -270,7 +270,7 @@ ${body.bounce?.bouncedRecipients.map(
}, },
(err, info) => { (err, info) => {
logger.log("sns-error", err ? "error" : "debug", "api", null, { logger.log("sns-error", err ? "error" : "debug", "api", null, {
message: err ? JSON.stringify(error) : info message: err?.message ?? info
}); });
} }
); );

View File

@@ -10,6 +10,7 @@ const generateEmailTemplate = require("./generateTemplate");
const moment = require("moment-timezone"); const moment = require("moment-timezone");
const { taskEmailQueue } = require("./tasksEmailsQueue"); const { taskEmailQueue } = require("./tasksEmailsQueue");
const mailer = require("./mailer"); const mailer = require("./mailer");
const { InstanceEndpoints } = require("../utils/instanceMgr");
// Initialize the Tasks Email Queue // Initialize the Tasks Email Queue
const tasksEmailQueue = taskEmailQueue(); const tasksEmailQueue = taskEmailQueue();
@@ -83,15 +84,8 @@ const formatPriority = (priority) => {
* @param taskId * @param taskId
* @returns {{header, body: string, subHeader: string}} * @returns {{header, body: string, subHeader: string}}
*/ */
const getEndpoints = (bodyshop) =>
InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome: process.env?.NODE_ENV === "test" ? "https//test.romeonline.io" : "https://romeonline.io"
});
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine, createdBy) => { const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine, createdBy) => {
const endPoints = getEndpoints(bodyshop); const endPoints = InstanceEndpoints();
return { return {
header: title, header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)} | Created By: ${createdBy || "N/A"}`, subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)} | Created By: ${createdBy || "N/A"}`,
@@ -108,9 +102,8 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
* @param html * @param html
* @param taskIds * @param taskIds
* @param successCallback * @param successCallback
* @param requestInstance
*/ */
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => { const sendMail = (type, to, subject, html, taskIds, successCallback) => {
const fromEmails = InstanceManager({ const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>", imex: "ImEX Online <noreply@imex.online>",
rome: "Rome Online <noreply@romeonline.io>" rome: "Rome Online <noreply@romeonline.io>"
@@ -136,7 +129,7 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
}; };
/** /**
* Send an email to the assigned user. * Email the assigned user.
* @param req * @param req
* @param res * @param res
* @returns {Promise<*>} * @returns {Promise<*>}
@@ -186,7 +179,7 @@ const taskAssignedEmail = async (req, res) => {
}; };
/** /**
* Send an email to remind the user of their tasks. * Email remind the user of their tasks.
* @param req * @param req
* @param res * @param res
* @returns {Promise<*>} * @returns {Promise<*>}
@@ -264,11 +257,6 @@ const tasksRemindEmail = async (req, res) => {
} }
// There are multiple emails to send to this author. // There are multiple emails to send to this author.
else { else {
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome: process.env?.NODE_ENV === "test" ? "https//test.romeonline.io" : "https://romeonline.io"
});
const allTasks = groupedTasks[recipient.email]; const allTasks = groupedTasks[recipient.email];
emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`; emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`;
emailData.html = generateEmailTemplate({ emailData.html = generateEmailTemplate({
@@ -278,7 +266,7 @@ const tasksRemindEmail = async (req, res) => {
body: `<ul> body: `<ul>
${allTasks ${allTasks
.map((task) => .map((task) =>
`<li><a href="${endPoints}/manage/tasks/alltasks?taskid=${task.id}">${task.title} - Priority: ${formatPriority(task.priority)} ${task.due_date ? `${formatDate(task.due_date)}` : ""} | Bodyshop: ${task.bodyshop.shopname}</a></li>`.trim() `<li><a href="${InstanceEndpoints()}/manage/tasks/alltasks?taskid=${task.id}">${task.title} - Priority: ${formatPriority(task.priority)} ${task.due_date ? `${formatDate(task.due_date)}` : ""} | Bodyshop: ${task.bodyshop.shopname}</a></li>`.trim()
) )
.join("")} .join("")}
</ul>` </ul>`
@@ -338,6 +326,5 @@ const tasksRemindEmail = async (req, res) => {
module.exports = { module.exports = {
taskAssignedEmail, taskAssignedEmail,
tasksRemindEmail, tasksRemindEmail
getEndpoints
}; };

View File

@@ -10,12 +10,11 @@ const moment = require("moment");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const { sendTaskEmail } = require("../email/sendemail"); const { sendTaskEmail } = require("../email/sendemail");
const generateEmailTemplate = require("../email/generateTemplate"); const generateEmailTemplate = require("../email/generateTemplate");
const { getEndpoints } = require("../email/tasksEmails");
const domain = process.env.NODE_ENV ? "secure" : "test"; const domain = process.env.NODE_ENV ? "secure" : "test";
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager"); const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const { InstanceRegion } = require("../utils/instanceMgr"); const { InstanceRegion, InstanceEndpoints } = require("../utils/instanceMgr");
const client = new SecretsManagerClient({ const client = new SecretsManagerClient({
region: InstanceRegion() region: InstanceRegion()
@@ -443,31 +442,28 @@ exports.postback = async (req, res) => {
}); });
if (values.origin === "OneLink" && parsedComment.userEmail) { if (values.origin === "OneLink" && parsedComment.userEmail) {
try { sendTaskEmail({
const endPoints = getEndpoints(); to: parsedComment.userEmail,
sendTaskEmail({ subject: `New Payment(s) Received - RO ${jobs.jobs.map((j) => j.ro_number).join(", ")}`,
to: parsedComment.userEmail, type: "html",
subject: `New Payment(s) Received - RO ${jobs.jobs.map((j) => j.ro_number).join(", ")}`, html: generateEmailTemplate({
type: "html", header: "New Payment(s) Received",
html: generateEmailTemplate({ subHeader: "",
header: "New Payment(s) Received", body: jobs.jobs
subHeader: "", .map(
body: jobs.jobs (job) =>
.map( `Reference: <a href="${InstanceEndpoints()}/manage/jobs/${job.id}">${job.ro_number || "N/A"}</a> | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()} | $${partialPayments.find((p) => p.jobid === job.id).amount}`
(job) => )
`Reference: <a href="${endPoints}/manage/jobs/${job.id}">${job.ro_number || "N/A"}</a> | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()} | $${partialPayments.find((p) => p.jobid === job.id).amount}` .join("<br/>")
) })
.join("<br/>") }).catch((error) => {
})
});
} catch (error) {
logger.log("intellipay-postback-email-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-email-error", "ERROR", req.user?.email, null, {
message: error.message, message: error.message,
jobs, jobs,
paymentResult, paymentResult,
...logResponseMeta ...logResponseMeta
}); });
} });
} }
res.sendStatus(200); res.sendStatus(200);
} else if (values.invoice) { } else if (values.invoice) {

View File

@@ -1,13 +1,20 @@
const { Queue, Worker } = require("bullmq"); const { Queue, Worker } = require("bullmq");
// Base time-related constant (in milliseconds) // Base time-related constant in minutes, sourced from environment variable or defaulting to 1
const CONSOLIDATION_DELAY = 60000; // 1 minute (base timeout) const APP_CONSOLIDATION_DELAY_IN_MINS = (() => {
const envValue = process.env?.APP_CONSOLIDATION_DELAY_IN_MINS;
const parsedValue = envValue ? parseInt(envValue, 10) : NaN;
return isNaN(parsedValue) ? 1 : Math.max(1, parsedValue); // Default to 1, ensure at least 1
})();
// Derived time-related constants based on CONSOLIDATION_DELAY / DO NOT TOUCH, these are pegged to CONSOLIDATION_DELAY // Base time-related constant (in milliseconds)
const NOTIFICATION_STORAGE_EXPIRATION = CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, for notification storage) const APP_CONSOLIDATION_DELAY = APP_CONSOLIDATION_DELAY_IN_MINS * 60000; // 1 minute (base timeout)
const CONSOLIDATION_FLAG_EXPIRATION = CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, buffer for consolidation flag)
const LOCK_EXPIRATION = CONSOLIDATION_DELAY * 0.25; // 15 seconds (quarter of base, for lock duration) // Derived time-related constants based on APP_CONSOLIDATION_DELAY / DO NOT TOUCH, these are pegged to APP_CONSOLIDATION_DELAY
const RATE_LIMITER_DURATION = CONSOLIDATION_DELAY * 0.1; // 6 seconds (tenth of base, for rate limiting) const NOTIFICATION_STORAGE_EXPIRATION = APP_CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, for notification storage)
const CONSOLIDATION_FLAG_EXPIRATION = APP_CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, buffer for consolidation flag)
const LOCK_EXPIRATION = APP_CONSOLIDATION_DELAY * 0.25; // 15 seconds (quarter of base, for lock duration)
const RATE_LIMITER_DURATION = APP_CONSOLIDATION_DELAY * 0.1; // 6 seconds (tenth of base, for rate limiting)
let addQueue; let addQueue;
let consolidateQueue; let consolidateQueue;
@@ -74,7 +81,7 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
{ jobId, recipients }, { jobId, recipients },
{ {
jobId: `consolidate:${jobId}`, jobId: `consolidate:${jobId}`,
delay: CONSOLIDATION_DELAY, delay: APP_CONSOLIDATION_DELAY,
attempts: 3, // Retry up to 3 times attempts: 3, // Retry up to 3 times
backoff: LOCK_EXPIRATION // Retry delay matches lock expiration (15s) backoff: LOCK_EXPIRATION // Retry delay matches lock expiration (15s)
} }

View File

@@ -1,8 +1,16 @@
const { Queue, Worker } = require("bullmq"); const { Queue, Worker } = require("bullmq");
const { sendTaskEmail } = require("../../email/sendemail"); const { sendTaskEmail } = require("../../email/sendemail");
const generateEmailTemplate = require("../../email/generateTemplate");
const { InstanceEndpoints } = require("../../utils/instanceMgr");
const EMAIL_CONSOLIDATION_DELAY_IN_MINS = (() => {
const envValue = process.env?.APP_CONSOLIDATION_DELAY_IN_MINS;
const parsedValue = envValue ? parseInt(envValue, 10) : NaN;
return isNaN(parsedValue) ? 1 : Math.max(1, parsedValue); // Default to 1, ensure at least 1
})();
// Base time-related constant (in milliseconds) // Base time-related constant (in milliseconds)
const EMAIL_CONSOLIDATION_DELAY = 60000; // 1 minute (base timeout) const EMAIL_CONSOLIDATION_DELAY = EMAIL_CONSOLIDATION_DELAY_IN_MINS * 60000; // 1 minute (base timeout)
// Derived time-related constants based on EMAIL_CONSOLIDATION_DELAY / DO NOT TOUCH, these are pegged to EMAIL_CONSOLIDATION_DELAY // Derived time-related constants based on EMAIL_CONSOLIDATION_DELAY / DO NOT TOUCH, these are pegged to EMAIL_CONSOLIDATION_DELAY
const CONSOLIDATION_KEY_EXPIRATION = EMAIL_CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, buffer for consolidation) const CONSOLIDATION_KEY_EXPIRATION = EMAIL_CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, buffer for consolidation)
@@ -109,27 +117,23 @@ const loadEmailQueue = async ({ pubClient, logger }) => {
const details = await pubClient.hgetall(detailsKey); const details = await pubClient.hgetall(detailsKey);
const firstName = details.firstName || "User"; const firstName = details.firstName || "User";
const subject = `Updates for job ${jobRoNumber} at ${bodyShopName}`; const subject = `Updates for job ${jobRoNumber} at ${bodyShopName}`;
const body = [ // Use the template instead of inline HTML
'<html lang="en"><body>', const emailBody = generateEmailTemplate({
"Dear " + firstName + ",", header: `Updates for Job ${jobRoNumber}`,
"", subHeader: `Dear ${firstName},`,
"There have been updates to job " + jobRoNumber + ":", body: `
"", <p>There have been updates to job ${jobRoNumber} at ${bodyShopName}:</p><br/>
"<ul>", <ul>
...messages.map((msg) => " <li>" + msg + "</li>"), ${messages.map((msg) => `<li>${msg}</li>`).join("")}
"</ul>", </ul><br/><br/>
"", <p><a href="${InstanceEndpoints()}/manage/jobs/${jobId}">Please check the job for more details.</a></p>
"Please check the job for more details.", `
"", });
"Best regards,",
bodyShopName,
"</body></html>"
].join("\n");
await sendTaskEmail({ await sendTaskEmail({
to: recipient, to: recipient,
subject, subject,
type: "html", type: "html",
html: body html: emailBody
}); });
logger.logger.info( logger.logger.info(
`Sent consolidated email to ${recipient} for jobId ${jobId} with ${messages.length} updates` `Sent consolidated email to ${recipient} for jobId ${jobId} with ${messages.length} updates`

View File

@@ -58,4 +58,20 @@ exports.InstanceRegion = () =>
rome: "us-east-2" rome: "us-east-2"
}); });
exports.InstanceEndpoints = () =>
InstanceManager({
imex:
process.env?.NODE_ENV === "development"
? "https://localhost:3000"
: process.env?.NODE_ENV === "test"
? "https://test.imex.online"
: "https://imex.online",
rome:
process.env?.NODE_ENV === "development"
? "https://localhost:3000"
: process.env?.NODE_ENV === "test"
? "https://test.romeonline.io"
: "https://romeonline.io"
});
exports.default = InstanceManager; exports.default = InstanceManager;