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 queries = require("../../graphql-client/queries");
const { parse, stringify } = require("querystring");
const InstanceManager = require("../../utils/instanceMgr").default;
const { InstanceEndpoints } = require("../../utils/instanceMgr");
const oauthClient = new OAuthClient({
clientId: process.env.QBO_CLIENT_ID,
@@ -17,16 +17,8 @@ const oauthClient = new OAuthClient({
logging: true
});
let url;
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`;
}
//TODO:AIO Add in QBO callbacks.
const url = InstanceEndpoints();
exports.default = async (req, res) => {
const queryString = req.url.split("?").reverse()[0];

View File

@@ -239,24 +239,24 @@ const emailBounce = async (req, res) => {
return;
}
//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,
status: "Bounced",
context: message.bounce?.bouncedRecipients
});
mailer.sendMail(
{
from: InstanceMgr({
from: InstanceManager({
imex: `ImEX Online <noreply@imex.online>`,
rome: `Rome Online <noreply@romeonline.io>`
}),
to: replyTo,
//bcc: "patrick@snapt.ca",
subject: `${InstanceMgr({
subject: `${InstanceManager({
imex: "ImEX Online",
rome: "Rome Online"
})} Bounced Email - RE: ${subject}`,
text: `${InstanceMgr({
text: `${InstanceManager({
imex: "ImEX Online",
rome: "Rome Online"
})} 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) => {
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 { taskEmailQueue } = require("./tasksEmailsQueue");
const mailer = require("./mailer");
const { InstanceEndpoints } = require("../utils/instanceMgr");
// Initialize the Tasks Email Queue
const tasksEmailQueue = taskEmailQueue();
@@ -83,15 +84,8 @@ const formatPriority = (priority) => {
* @param taskId
* @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 endPoints = getEndpoints(bodyshop);
const endPoints = InstanceEndpoints();
return {
header: title,
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 taskIds
* @param successCallback
* @param requestInstance
*/
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
const sendMail = (type, to, subject, html, taskIds, successCallback) => {
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
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 res
* @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 res
* @returns {Promise<*>}
@@ -264,11 +257,6 @@ const tasksRemindEmail = async (req, res) => {
}
// There are multiple emails to send to this author.
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];
emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`;
emailData.html = generateEmailTemplate({
@@ -278,7 +266,7 @@ const tasksRemindEmail = async (req, res) => {
body: `<ul>
${allTasks
.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("")}
</ul>`
@@ -338,6 +326,5 @@ const tasksRemindEmail = async (req, res) => {
module.exports = {
taskAssignedEmail,
tasksRemindEmail,
getEndpoints
tasksRemindEmail
};

View File

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

View File

@@ -1,13 +1,20 @@
const { Queue, Worker } = require("bullmq");
// Base time-related constant (in milliseconds)
const CONSOLIDATION_DELAY = 60000; // 1 minute (base timeout)
// Base time-related constant in minutes, sourced from environment variable or defaulting to 1
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
const NOTIFICATION_STORAGE_EXPIRATION = CONSOLIDATION_DELAY * 1.5; // 1.5 minutes (90s, for notification storage)
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)
const RATE_LIMITER_DURATION = CONSOLIDATION_DELAY * 0.1; // 6 seconds (tenth of base, for rate limiting)
// Base time-related constant (in milliseconds)
const APP_CONSOLIDATION_DELAY = APP_CONSOLIDATION_DELAY_IN_MINS * 60000; // 1 minute (base timeout)
// Derived time-related constants based on APP_CONSOLIDATION_DELAY / DO NOT TOUCH, these are pegged to APP_CONSOLIDATION_DELAY
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 consolidateQueue;
@@ -74,7 +81,7 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
{ jobId, recipients },
{
jobId: `consolidate:${jobId}`,
delay: CONSOLIDATION_DELAY,
delay: APP_CONSOLIDATION_DELAY,
attempts: 3, // Retry up to 3 times
backoff: LOCK_EXPIRATION // Retry delay matches lock expiration (15s)
}

View File

@@ -1,8 +1,16 @@
const { Queue, Worker } = require("bullmq");
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)
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
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 firstName = details.firstName || "User";
const subject = `Updates for job ${jobRoNumber} at ${bodyShopName}`;
const body = [
'<html lang="en"><body>',
"Dear " + firstName + ",",
"",
"There have been updates to job " + jobRoNumber + ":",
"",
"<ul>",
...messages.map((msg) => " <li>" + msg + "</li>"),
"</ul>",
"",
"Please check the job for more details.",
"",
"Best regards,",
bodyShopName,
"</body></html>"
].join("\n");
// Use the template instead of inline HTML
const emailBody = generateEmailTemplate({
header: `Updates for Job ${jobRoNumber}`,
subHeader: `Dear ${firstName},`,
body: `
<p>There have been updates to job ${jobRoNumber} at ${bodyShopName}:</p><br/>
<ul>
${messages.map((msg) => `<li>${msg}</li>`).join("")}
</ul><br/><br/>
<p><a href="${InstanceEndpoints()}/manage/jobs/${jobId}">Please check the job for more details.</a></p>
`
});
await sendTaskEmail({
to: recipient,
subject,
type: "html",
html: body
html: emailBody
});
logger.logger.info(
`Sent consolidated email to ${recipient} for jobId ${jobId} with ${messages.length} updates`

View File

@@ -58,4 +58,20 @@ exports.InstanceRegion = () =>
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;