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:
@@ -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];
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user