Files
bodyshop/server/email/tasksEmails.js
2024-04-19 12:41:26 -07:00

303 lines
9.3 KiB
JavaScript

const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let nodemailer = require("nodemailer");
let aws = require("@aws-sdk/client-ses");
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const generateEmailTemplate = require("./generateTemplate");
const moment = require("moment");
const { taskEmailQueue } = require("./tasksEmailsQueue");
const ses = new aws.SES({
apiVersion: "latest",
defaultProvider,
region: InstanceManager({
imex: "ca-central-1",
rome: "us-east-2"
})
});
const transporter = nodemailer.createTransport({
SES: { ses, aws },
sendingRate: 40 // 40 emails per second.
});
// Initialize the Tasks Email Queue
const tasksEmailQueue = taskEmailQueue();
// Cleanup function for the Tasks Email Queue
const tasksEmailQueueCleanup = async () => {
try {
// Example async operation
console.log("Performing Tasks Email Reminder process cleanup...");
await new Promise((resolve) => tasksEmailQueue.destroy(() => resolve()));
} catch (err) {
console.error("Tasks Email Reminder process cleanup failed:", err);
}
};
// Handling SIGINT (e.g., Ctrl+C)
process.on("SIGINT", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling SIGTERM (e.g., sent by system shutdown)
process.on("SIGTERM", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling uncaught exceptions
process.on("uncaughtException", async (err) => {
await tasksEmailQueueCleanup();
process.exit(1); // Exit with an 'error' code
});
// Handling unhandled promise rejections
process.on("unhandledRejection", async (reason, promise) => {
await tasksEmailQueueCleanup();
process.exit(1); // Exit with an 'error' code
});
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
rome: "Rome Online <noreply@romeonline.io>",
promanager: "ProManager <noreply@promanager.web-est.com>"
});
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",
promanager: process.env?.NODE_ENV === "test" ? "https//test.promanager.web-est.com" : "https://promanager.web-est.com"
});
/**
* Format the date for the email.
* @param date
* @returns {string|string}
*/
const formatDate = (date) => {
return date ? `| Due on: ${moment(date).format("MM/DD/YYYY")}` : "";
};
const formatPriority = (priority) => {
if (priority === 1) {
return "High";
} else if (priority === 3) {
return "Low";
} else {
return "Medium";
}
};
/**
* Generate the email template arguments.
* @param title
* @param priority
* @param description
* @param dueDate
* @param bodyshop
* @param job
* @param taskId
* @returns {{header, body: string, subHeader: string}}
*/
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
return {
header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
body: `Reference: ${job.ro_number || "N/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()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`
};
};
/**
* Send the email.
* @param type
* @param to
* @param subject
* @param html
* @param taskIds
* @param successCallback
*/
const sendMail = (type, to, subject, html, taskIds, successCallback) => {
// Push next messages to Nodemailer
//transporter.once("idle", () => {
// Note: This is commented out because despite being in the documentation, it does not work
// and stackoverflow suggests it is not needed
// if (transporter.isIdle()) {
transporter.sendMail(
{
from: fromEmails,
to,
subject,
html
},
(error, info) => {
if (info) {
if (typeof successCallback === "function" && taskIds && taskIds.length) {
successCallback(taskIds);
}
} else {
logger.log(`task-${type}-email-failure`, "error", null, null, error);
}
}
);
// }
// });
};
/**
* Send an email to the assigned user.
* @param req
* @param res
* @returns {Promise<*>}
*/
const taskAssignedEmail = async (req, res) => {
// We have no event Data, bail
if (!req?.body?.event?.data?.new) {
return res.status(400).json({ message: "No data in the event body" });
}
const { new: newTask } = req.body.event.data;
// This is not a new task, but a reassignment.
const dirty = req.body.event.data?.old && req.body.event.data?.old?.assigned_to;
//Query to get the employee assigned currently.
const { tasks_by_pk } = await client.request(queries.QUERY_TASK_BY_ID, {
id: newTask.id
});
sendMail(
"assigned",
tasks_by_pk.assigned_to_employee.user_email,
`A ${formatPriority(newTask.priority)} priority task has been ${dirty ? "reassigned to" : "created for"} you - ${newTask.title}`,
generateEmailTemplate(
generateTemplateArgs(
newTask.title,
newTask.priority,
newTask.description,
newTask.due_date,
tasks_by_pk.bodyshop,
tasks_by_pk.job,
newTask.id
)
)
);
// We return success regardless because we don't want to block the event trigger.
res.status(200).json({ success: true });
};
/**
* Send an email to remind the user of their tasks.
* @param req
* @param res
* @returns {Promise<*>}
*/
const tasksRemindEmail = async (req, res) => {
try {
const tasksRequest = await client.request(queries.QUERY_REMIND_TASKS, {
time: moment().add(1, "minutes").toISOString()
});
// No tasks present in the database, bail.
if (!tasksRequest?.tasks || !tasksRequest?.tasks.length) {
return res.status(200).json({ message: "No tasks to remind" });
}
// Group tasks by assigned_to, to avoid sending multiple emails to the same recipient.
const groupedTasks = tasksRequest.tasks.reduce((acc, task) => {
const key = task.assigned_to_employee.user_email;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(task);
return acc;
}, {});
// No grouped tasks, bail.
if (Object.keys(groupedTasks).length === 0) {
return res.status(200).json({ message: "No tasks to remind" });
}
// Build an aggregate data object containing email and the count of tasks assigned to them.
const recipientCounts = Object.keys(groupedTasks).map((key) => {
return {
email: key,
count: groupedTasks[key].length
};
});
// Iterate over all recipients and send the email.
recipientCounts.forEach((recipient) => {
const emailData = {
from: fromEmails,
to: recipient.email
};
const taskIds = groupedTasks[recipient.email].map((task) => task.id);
// There is only the one email to send to this author.
if (recipient.count === 1) {
const onlyTask = groupedTasks[recipient.email][0];
emailData.subject =
`New ${formatPriority(onlyTask.priority)} Priority Task Reminder - ${onlyTask.title} ${onlyTask.due_date ? `- ${formatDate(onlyTask.due_date)}` : ""}`.trim();
emailData.html = generateEmailTemplate(
generateTemplateArgs(
onlyTask.title,
onlyTask.priority,
onlyTask.description,
onlyTask.due_date,
onlyTask.bodyshop,
onlyTask.job,
onlyTask.id
)
);
}
// There are multiple emails to send to this author.
else {
const allTasks = groupedTasks[recipient.email];
emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`;
emailData.html = generateEmailTemplate({
header: `${allTasks.length} Tasks require your attention`,
subHeader: `Please click on the Tasks below to view the Task.`,
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()
)
.join("")}
</ul>`
});
}
if (emailData?.subject && emailData?.html) {
// Send Email
sendMail("remind", emailData.to, emailData.subject, emailData.html, taskIds, (taskIds) => {
for (const taskId of taskIds) {
tasksEmailQueue.push(taskId);
}
});
}
});
// Sixth step would be to set the remind_at_sent to the current time.
res.status(200).json({ status: "success" });
} catch (err) {
res.status(500).json({
status: "error",
message: `Something went wrong sending Task Reminders: ${err.message || "An error occurred"}`
});
}
};
module.exports = {
taskAssignedEmail,
tasksRemindEmail
};