From 1c3c7e115ae3dce52fb096b960a0dabd4bb08668 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 19 Jul 2024 14:35:50 -0700 Subject: [PATCH] Refactored email service for modernity and clarity --- Server/index.js | 79 ++++++---------- Server/service/emailService.js | 162 ++++++++++----------------------- 2 files changed, 76 insertions(+), 165 deletions(-) diff --git a/Server/index.js b/Server/index.js index 306776643..23e5891db 100644 --- a/Server/index.js +++ b/Server/index.js @@ -1,26 +1,24 @@ const express = require("express"); const helmet = require("helmet"); const cors = require("cors"); -const authRouter = require("./routes/authRoute"); -const monitorRouter = require("./routes/monitorRoute"); -const checkRouter = require("./routes/checkRoute"); -const alertRouter = require("./routes/alertRoute"); -const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); const logger = require("./utils/logger"); const { verifyJWT } = require("./middleware/verifyJWT"); const { handleErrors } = require("./middleware/handleErrors"); + +const authRouter = require("./routes/authRoute"); +const monitorRouter = require("./routes/monitorRoute"); +const checkRouter = require("./routes/checkRoute"); +const alertRouter = require("./routes/alertRoute"); +const pageSpeedCheckRouter = require("./routes/pageSpeedCheckRoute"); + +const { connectDbAndRunServer } = require("./configs/db"); const queueRouter = require("./routes/queueRoute"); const JobQueue = require("./service/jobQueue"); -const pageSpeedCheckRouter = require("./routes/pageSpeedCheckRoute"); -const nodemailer = require("nodemailer"); - -const emailService = require("./service/emailService"); +const EmailService = require("./service/emailService"); // Need to wrap server setup in a function to handle async nature of JobQueue const startApp = async () => { - // const { sendEmail } = require('./utils/sendEmail') - // ************************** // Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out. // Simply comment out the FakeDB and uncomment the MongoDB or vice versa. @@ -42,12 +40,6 @@ const startApp = async () => { ? DB_TYPE[process.env.DB_TYPE]() : require("./db/FakeDb"); - /** - * NOTES - * Email Service will be added - * Logger Service will be added (Winston or similar) - */ - const app = express(); // middlewares @@ -62,11 +54,12 @@ const startApp = async () => { // Make DB accessible anywhere we have a Request object // By adding the DB to the request object, we can access it in any route // Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it - // Same applies for JobQueue + // Same applies for JobQueue and emailService // ************************** app.use((req, res, next) => { req.db = db; req.jobQueue = jobQueue; + req.emailSerivce = emailSerivce; next(); }); @@ -90,39 +83,21 @@ const startApp = async () => { } }); - // Nodemailer code here - const transporter = nodemailer.createTransport({ - host: process.env.EMAIL_SERVICE_HOST, - port: process.env.EMAIL_SERVICE_PORT, - auth: { - user: process.env.EMAIL_SERVICE_USERNAME, - pass: process.env.EMAIL_SERVICE_PASSWORD, - }, - }); - - app.use("/api/v1/mail", (req, res) => { - console.log("Started"); - // Replacing varibales - const context = { name: "Alex" }; - - // Define mail options - const mailOptions = { - from: "BlueWave Uptime ", // sender address - to: "muhammadkhalilzadeh1998@gmailc.com", // list of receivers - subject: "Testing template emails", // Subject line - html: emailService.sendWelcomeEmail(context), // html body - }; - - // Send mail with defined transport object - transporter.sendMail(mailOptions, (error, info) => { - if (error) { - return res - .status(500) - .send({ message: "Error sending email", error: error }); - } - console.log(info); - res.status(200).send({ message: "Email sent successfully", info: info }); - }); + app.use("/api/v1/mail", async (req, res) => { + try { + const id = await req.emailSerivce.buildAndSendEmail( + "welcomeEmailTemplate", + { + name: "Alex", + }, + "ajhollid@gmail.com", + "Welcome" + ); + res.status(200).json({ success: true, msg: "Email sent", data: id }); + } catch (error) { + logger.error(error.message); + return res.status(500).json({ message: error.message }); + } }); /** @@ -131,8 +106,10 @@ const startApp = async () => { */ app.use(handleErrors); + // Create services await connectDbAndRunServer(app, db); const jobQueue = await JobQueue.createJobQueue(db); + const emailSerivce = new EmailService(); const cleanup = async () => { console.log("Shutting down gracefully"); diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 235913b18..d44a20bb4 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -1,125 +1,59 @@ const fs = require("fs"); const path = require("path"); +const nodemailer = require("nodemailer"); const { compile } = require("handlebars"); const { mjml2html } = require("mjml"); -// Fetching Templates +class EmailService { + constructor() { + this.loadTemplate = (templateName) => { + const templatePath = path.join( + __dirname, + `../templates/${templateName}.mjml` + ); + const templateContent = fs.readFileSync(templatePath, "utf8"); + return compile(templateContent); + }; -// Welcome Email Template -const welcomeEmailTemplatePath = path.join( - __dirname, - "../templates/welcomeEmail.mjml" -); -const welcomeEmailTemplateContent = fs.readFileSync( - welcomeEmailTemplatePath, - "utf8" -); -const welcomeEmailTemplate = compile(welcomeEmailTemplateContent); + // TODO Load less used templates in their respective functions + this.templateLookup = { + welcomeEmailTemplate: this.loadTemplate("welcomeEmail"), + employeeActivationTemplate: this.loadTemplate("employeeActivation"), + noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"), + serverIsDownTemplate: this.loadTemplate("serverIsDown"), + serverIsUpTemplate: this.loadTemplate("serverIsUp"), + passwordResetTemplate: this.loadTemplate("passwordReset"), + }; -// Employee Activation Email Template -const employeeActivationTemplatePath = path.join( - __dirname, - "../templates/employeeActivation.mjml" -); -const employeeActivationTemplateContent = fs.readFileSync( - employeeActivationTemplatePath, - "utf8" -); -const employeeActivation = compile(employeeActivationTemplateContent); + this.transporter = nodemailer.createTransport({ + host: process.env.EMAIL_SERVICE_HOST, + port: process.env.EMAIL_SERVICE_PORT, + secure: true, // Use `true` for port 465, `false` for all other ports + auth: { + user: process.env.EMAIL_SERVICE_USERNAME, + pass: process.env.EMAIL_SERVICE_PASSWORD, + }, + }); + } -// No Incident This Week Template -const noIncidentsThisWeekTemplatePath = path.join( - __dirname, - "../templates/noIncidentsThisWeek.mjml" -); -const noIncidentsThisWeekTemplateContent = fs.readFileSync( - noIncidentsThisWeekTemplatePath, - "utf8" -); -const noIncidentsThisWeek = compile(noIncidentsThisWeekTemplateContent); + buildAndSendEmail = async (template, context, to, subject) => { + const buildHtml = (template, context) => { + const mjml = this.templateLookup[template](context); + const html = mjml2html(mjml); + return html; + }; -// Server is Down Template -const serverIsDownTemplatePath = path.join( - __dirname, - "../templates/serverIsDown.mjml" -); -const serverIsDownTemplateContent = fs.readFileSync( - serverIsDownTemplatePath, - "utf8" -); -const serverIsDown = compile(serverIsDownTemplateContent); - -// Server is Up Template -const serverIsUpTemplatePath = path.join( - __dirname, - "../templates/serverIsUp.mjml" -); -const serverIsUpTemplateContent = fs.readFileSync( - serverIsUpTemplatePath, - "utf8" -); -const serverIsUp = compile(serverIsUpTemplateContent); - -// Password Reset Template -const passwordResetTemplatePath = path.join( - __dirname, - "../templates/passwordReset.mjml" -); -const passwordResetTemplateContent = fs.readFileSync( - passwordResetTemplatePath, - "utf8" -); -const passwordReset = compile(passwordResetTemplateContent); - -// *** Application specific functions *** - -function sendWelcomeEmail(context) { - const mjml = welcomeEmailTemplate(context); - const html = mjml2html(mjml); - - return html; + const sendEmail = async (to, subject, html) => { + const info = await this.transporter.sendMail({ + to: to, + subject: subject, + html: html, + }); + return info; + }; + const info = await sendEmail(to, subject, buildHtml(template, context)); + return info.messageId; + }; } -function sendEmployeeActivationEmail(context) { - const mjml = employeeActivation(context); - const html = mjml2html(mjml); - - return html; -} - -function sendNoIncidentsThisWeekEmail(context) { - const mjml = noIncidentsThisWeek(context); - const html = mjml2html(mjml); - - return html; -} - -function sendServerIsDownEmail(context) { - const mjml = serverIsDown(context); - const html = mjml2html(mjml); - - return html; -} - -function sendServerIsUpEmail(context) { - const mjml = serverIsUp(context); - const html = mjml2html(mjml); - - return html; -} - -function sendPasswordResetEmail(context) { - const mjml = passwordReset(context); - const html = mjml2html(mjml); - - return html; -} - -module.exports = { - sendWelcomeEmail, - sendEmployeeActivationEmail, - sendNoIncidentsThisWeekEmail, - sendServerIsDownEmail, - sendServerIsUpEmail, - sendPasswordResetEmail, -}; +module.exports = EmailService;