Merge pull request #2486 from bluewave-labs/refactor/build-and-send-email

refactor: build and send
This commit is contained in:
Alexander Holliday
2025-06-19 10:33:37 +08:00
committed by GitHub
7 changed files with 143 additions and 135 deletions
+32 -24
View File
@@ -7,7 +7,6 @@ import {
recoveryTokenValidation,
newPasswordValidation,
} from "../validation/joi.js";
import logger from "../utils/logger.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import crypto from "crypto";
@@ -15,12 +14,13 @@ import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "authController";
class AuthController {
constructor(db, settingsService, emailService, jobQueue, stringService) {
constructor({ db, settingsService, emailService, jobQueue, stringService, logger }) {
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
this.jobQueue = jobQueue;
this.stringService = stringService;
this.logger = logger;
}
/**
@@ -96,21 +96,28 @@ class AuthController {
const token = this.issueToken(userForToken, appSettings);
this.emailService
.buildAndSendEmail(
"welcomeEmailTemplate",
{ name: newUser.firstName },
newUser.email,
"Welcome to Uptime Monitor"
)
.catch((error) => {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "registerUser",
stack: error.stack,
});
try {
const html = await this.emailService.buildEmail("welcomeEmailTemplate", {
name: newUser.firstName,
});
this.emailService
.sendEmail(newUser.email, "Welcome to Uptime Monitor", html)
.catch((error) => {
this.logger.warn({
message: error.message,
service: SERVICE_NAME,
method: "registerUser",
stack: error.stack,
});
});
} catch (error) {
logger.warn({
message: error.message,
service: SERVICE_NAME,
method: "registerUser",
stack: error.stack,
});
}
res.success({
msg: this.stringService.authCreateUser,
@@ -300,15 +307,16 @@ class AuthController {
const name = user.firstName;
const { clientHost } = this.settingsService.getSettings();
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
const msgId = await this.emailService.buildAndSendEmail(
"passwordResetTemplate",
{
name,
email,
url,
},
const html = await this.emailService.buildEmail("passwordResetTemplate", {
name,
email,
"Checkmate Password Reset"
url,
});
const msgId = await this.emailService.sendEmail(
email,
"Checkmate Password Reset",
html
);
return res.success({
+18 -17
View File
@@ -71,24 +71,25 @@ class InviteController {
const inviteToken = await this.db.requestInviteToken({ ...req.body });
const { clientHost } = this.settingsService.getSettings();
this.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"
)
.catch((error) => {
logger.error({
message: error.message,
service: SERVICE_NAME,
method: "issueInvitation",
stack: error.stack,
});
try {
const html = await this.emailService.buildEmail("employeeActivationTemplate", {
name: firstname,
link: `${clientHost}/register/${inviteToken.token}`,
});
await this.emailService.sendEmail(
req.body.email,
"Welcome to Uptime Monitor",
html
);
} catch (error) {
logger.warn({
message: error.message,
service: SERVICE_NAME,
method: "sendInviteEmail",
stack: error.stack,
});
}
return res.success({
msg: this.stringService.inviteIssued,
+2 -6
View File
@@ -580,12 +580,8 @@ class MonitorController {
const subject = this.stringService.testEmailSubject;
const context = { testName: "Monitoring System" };
const messageId = await this.emailService.buildAndSendEmail(
"testEmailTemplate",
context,
to,
subject
);
const html = await this.emailService.buildEmail("testEmailTemplate", context);
const messageId = await this.emailService.sendEmail(to, subject, html);
if (!messageId) {
return res.error({
+16 -21
View File
@@ -90,26 +90,21 @@ class SettingsController {
const subject = this.stringService.testEmailSubject;
const context = { testName: "Monitoring System" };
const messageId = await this.emailService.buildAndSendEmail(
"testEmailTemplate",
context,
to,
subject,
{
systemEmailHost,
systemEmailPort,
systemEmailUser,
systemEmailAddress,
systemEmailPassword,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
}
);
const html = await this.emailService.buildEmail("testEmailTemplate", context);
const messageId = await this.emailService.sendEmail(to, subject, html, {
systemEmailHost,
systemEmailPort,
systemEmailUser,
systemEmailAddress,
systemEmailPassword,
systemEmailConnectionHost,
systemEmailSecure,
systemEmailPool,
systemEmailIgnoreTLS,
systemEmailRequireTLS,
systemEmailRejectUnauthorized,
systemEmailTLSServername,
});
if (!messageId) {
return res.error({
@@ -122,7 +117,7 @@ class SettingsController {
data: { messageId },
});
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
next(handleError(error, SERVICE_NAME));
return;
}
};
+8 -7
View File
@@ -239,13 +239,14 @@ const startApp = async () => {
process.on("SIGTERM", shutdown);
//Create controllers
const authController = new AuthController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(SettingsService.SERVICE_NAME),
ServiceRegistry.get(EmailService.SERVICE_NAME),
ServiceRegistry.get(JobQueue.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
);
const authController = new AuthController({
db: ServiceRegistry.get(MongoDB.SERVICE_NAME),
settingsService: ServiceRegistry.get(SettingsService.SERVICE_NAME),
emailService: ServiceRegistry.get(EmailService.SERVICE_NAME),
jobQueue: ServiceRegistry.get(JobQueue.SERVICE_NAME),
stringService: ServiceRegistry.get(StringService.SERVICE_NAME),
logger: logger,
});
const monitorController = new MonitorController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
+32 -49
View File
@@ -80,24 +80,28 @@ class EmailService {
*/
};
/**
* Asynchronously builds and sends an email using a specified template and context.
*
* @param {string} template - The name of the template to use for the email body.
* @param {Object} context - The data context to render the template with.
* @param {string} to - The recipient's email address.
* @param {string} subject - The subject of the email.
* @returns {Promise<string>} A promise that resolves to the messageId of the sent email.
*/
buildAndSendEmail = async (template, context, to, subject, transportConfig) => {
// TODO - Consider an update transporter method so this only needs to be recreated when smtp settings change
buildEmail = async (template, context) => {
try {
const mjml = this.templateLookup[template](context);
const html = await this.mjml2html(mjml);
return html.html;
} catch (error) {
this.logger.error({
message: error.message,
service: SERVICE_NAME,
method: "buildEmail",
stack: error.stack,
});
}
};
sendEmail = async (to, subject, html, transportConfig) => {
let config;
if (typeof transportConfig !== "undefined") {
config = transportConfig;
} else {
config = await this.settingsService.getDBSettings();
}
const {
systemEmailHost,
systemEmailPort,
@@ -131,45 +135,24 @@ class EmailService {
servername: systemEmailTLSServername,
},
};
this.transporter = this.nodemailer.createTransport(emailConfig);
const buildHtml = async (template, context) => {
try {
const mjml = this.templateLookup[template](context);
const html = await this.mjml2html(mjml);
return html.html;
} catch (error) {
this.logger.error({
message: error.message,
service: SERVICE_NAME,
method: "buildAndSendEmail",
stack: error.stack,
});
}
};
const sendEmail = async (to, subject, html) => {
try {
const info = await this.transporter.sendMail({
to: to,
from: systemEmailAddress,
subject: subject,
html: html,
});
return info;
} catch (error) {
this.logger.error({
message: error.message,
service: SERVICE_NAME,
method: "sendEmail",
stack: error.stack,
});
}
};
const html = await buildHtml(template, context);
const info = await sendEmail(to, subject, html);
return info?.messageId;
try {
const info = await this.transporter.sendMail({
to: to,
from: systemEmailAddress,
subject: subject,
html: html,
});
return info?.messageId;
} catch (error) {
this.logger.error({
message: error.message,
service: SERVICE_NAME,
method: "sendEmail",
stack: error.stack,
});
}
};
}
+35 -11
View File
@@ -218,7 +218,16 @@ class NotificationService {
const template = "hardwareIncidentTemplate";
const context = { monitor: monitor.name, url: monitor.url, alerts };
const subject = `Monitor ${monitor.name} infrastructure alerts`;
this.emailService.buildAndSendEmail(template, context, address, subject);
const html = await this.emailService.buildEmail(template, context);
this.emailService.sendEmail(address, subject, html).catch((error) => {
this.logger.warn({
message: error.message,
service: this.SERVICE_NAME,
method: "sendHardwareEmail",
stack: error.stack,
});
});
return true;
}
@@ -231,17 +240,22 @@ class NotificationService {
const subject = this.stringService.testEmailSubject;
const context = { testName: "Monitoring System" };
const messageId = await this.emailService.buildAndSendEmail(
"testEmailTemplate",
context,
to,
subject
);
try {
const html = await this.emailService.buildEmail("testEmailTemplate", context);
const messageId = await this.emailService.sendEmail(to, subject, html);
if (messageId) {
return true;
if (messageId) {
return true;
}
} catch (error) {
this.logger.warn({
message: error.message,
service: this.SERVICE_NAME,
method: "sendTestEmail",
stack: error.stack,
});
return false;
}
return false;
};
/**
@@ -259,7 +273,17 @@ class NotificationService {
const template = prevStatus === false ? "serverIsUpTemplate" : "serverIsDownTemplate";
const context = { monitor: monitor.name, url: monitor.url };
const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`;
this.emailService.buildAndSendEmail(template, context, address, subject);
const html = await this.emailService.buildEmail(template, context);
this.emailService.sendEmail(address, subject, html).catch((error) => {
this.logger.warn({
message: error.message,
service: this.SERVICE_NAME,
method: "sendEmail",
stack: error.stack,
});
});
return true;
}