From 29f0031c1e092ced78a9044871e499840e119142 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 18 Sep 2024 18:30:02 -0400 Subject: [PATCH 1/4] IO-2782-Send-Promanager-Welcome-Email - Send ProManager welcome email Signed-off-by: Dave Richer --- server/email/sendemail.js | 15 ++++ server/firebase/firebase-handler.js | 122 +++++++++++++++++++++++++++- server/routes/adminRoutes.js | 1 + 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/server/email/sendemail.js b/server/email/sendemail.js index 85534b815..878918f48 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -96,6 +96,20 @@ const sendServerEmail = async ({ subject, text }) => { } }; +const sendProManagerWelcomeEmail = async (to, subject, html) => { + try { + await transporter.sendMail({ + from: `ProManager `, + to, + subject, + html + }); + } catch (error) { + console.log(error); + logger.log("server-email-failure", "error", null, null, error); + } +}; + const sendTaskEmail = async ({ to, subject, text, attachments }) => { try { transporter.sendMail( @@ -309,5 +323,6 @@ module.exports = { sendEmail, sendServerEmail, sendTaskEmail, + sendProManagerWelcomeEmail, emailBounce }; diff --git a/server/firebase/firebase-handler.js b/server/firebase/firebase-handler.js index 509bedce9..2cee624dd 100644 --- a/server/firebase/firebase-handler.js +++ b/server/firebase/firebase-handler.js @@ -1,7 +1,7 @@ const admin = require("firebase-admin"); const logger = require("../utils/logger"); const path = require("path"); -const { auth } = require("firebase-admin"); +const { sendProManagerWelcomeEmail } = require("../email/sendemail"); require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) @@ -10,6 +10,7 @@ const client = require("../graphql-client/graphql-client").client; const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON); const adminEmail = require("../utils/adminEmail"); +const generateEmailTemplate = require("../email/generateTemplate"); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), @@ -24,7 +25,8 @@ exports.createUser = async (req, res) => { ioadmin: true }); - const { email, displayName, password, shopid, authlevel } = req.body; + const { email, displayName, password, shopid, authlevel, validemail } = req.body; + try { const userRecord = await admin.auth().createUser({ email, displayName, password }); @@ -42,6 +44,7 @@ exports.createUser = async (req, res) => { user: { email: email.toLowerCase(), authid: userRecord.uid, + validemail, associations: { data: [{ shopid, authlevel, active: true }] } @@ -58,6 +61,121 @@ exports.createUser = async (req, res) => { } }; +exports.promanagerWelcomeEmail = (req, res) => { + const { authid, email } = req.body; + + // Gate the operation to only admin users + if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { + logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, { + request: req.body, + user: req.user + }); + res.sendStatus(404); + return; + } + + admin + .auth() + .getUser(authid) + .then((userRecord) => { + if (!userRecord) { + res.status(404).json({ message: "User not found in Firebase." }); + return Promise.reject("User not found in Firebase."); + } + + // Fetch user data from the database using GraphQL + return client.request( + ` + query GET_USER_BY_EMAIL($email: String!) { + users(where: { email: { _eq: $email } }) { + email + validemail + associations { + id + shopid + bodyshop { + id + convenient_company + } + } + } + } + `, + { email: email.toLowerCase() } + ); + }) + .then((dbUserResult) => { + const dbUser = dbUserResult?.users?.[0]; + if (!dbUser) { + res.status(404).json({ message: "User not found in database." }); + return Promise.reject("User not found in database."); + } + + // Check if the email is valid before proceeding + if (!dbUser.validemail) { + logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, { + message: "User email is not valid, skipping email.", + email + }); + return res.status(200).json({ message: "User email is not valid, email not sent." }); + } + + // Check if convenient_company is equal to "promanager" + const convenientCompany = dbUser.associations?.[0]?.bodyshop?.convenient_company; + if (convenientCompany !== "promanager") { + logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, { + message: `convenient_company is not "promanager", skipping email.`, + convenientCompany + }); + return res.status(200).json({ message: `convenient_company is not "promanager", email not sent.` }); + } + + // Generate password reset link + return admin + .auth() + .generatePasswordResetLink(dbUser.email) + .then((resetLink) => ({ + dbUser, + resetLink + })); + }) + .then(({ dbUser, resetLink }) => { + // Send email logic here (replace this with your email-sending service) + return sendProManagerWelcomeEmail({ + to: dbUser.email, + subject: "Welcome to the ProManager platform.", + html: generateEmailTemplate({ + header: "", + subHeader: "", + body: ` +

Welcome to the ProManager platform. Please click the link below to reset your password:

+

Reset your password

+

User Details:

+
    +
  • Email: ${dbUser.email}
  • +
+ ` + }) + }); + }) + .then(() => { + logger.log("admin-send-welcome-email", "ADMIN", req.user.email, null, { + request: req.body, + ioadmin: true, + emailSentTo: email + }); + res.status(200).json({ message: "Welcome email sent successfully." }); + }) + .catch((error) => { + logger.log("admin-send-welcome-email-error", "ERROR", req.user.email, null, { + error + }); + if (!res.headersSent) { + res.status(500).json({ message: "Error sending welcome email.", error }); + } + }); +}; + exports.updateUser = (req, res) => { logger.log("admin-update-user", "ADMIN", req.user.email, null, { request: req.body, diff --git a/server/routes/adminRoutes.js b/server/routes/adminRoutes.js index c1d3fe85a..74775724d 100644 --- a/server/routes/adminRoutes.js +++ b/server/routes/adminRoutes.js @@ -14,5 +14,6 @@ router.post("/updatecounter", validateAdminMiddleware, updateCounter); router.post("/updateuser", fb.updateUser); router.post("/getuser", fb.getUser); router.post("/createuser", fb.createUser); +router.post("/promanagerwelcome", fb.promanagerWelcomeEmail); module.exports = router; From cdb2d4d2d6fd3da3fe807431598e8ea55a929064 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 19 Sep 2024 11:29:13 -0400 Subject: [PATCH 2/4] IO-2782-Send-Promanager-Welcome-Email - Cleanup of adminRoutes / firebase-handler.js Signed-off-by: Dave Richer --- server/firebase/firebase-handler.js | 129 ++++++++++++---------------- server/routes/adminRoutes.js | 19 ++-- 2 files changed, 64 insertions(+), 84 deletions(-) diff --git a/server/firebase/firebase-handler.js b/server/firebase/firebase-handler.js index 2cee624dd..7223d89a8 100644 --- a/server/firebase/firebase-handler.js +++ b/server/firebase/firebase-handler.js @@ -1,11 +1,11 @@ -const admin = require("firebase-admin"); -const logger = require("../utils/logger"); const path = require("path"); -const { sendProManagerWelcomeEmail } = require("../email/sendemail"); - require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); + +const admin = require("firebase-admin"); +const logger = require("../utils/logger"); +const { sendProManagerWelcomeEmail } = require("../email/sendemail"); const client = require("../graphql-client/graphql-client").client; const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON); @@ -17,9 +17,7 @@ admin.initializeApp({ databaseURL: process.env.FIREBASE_DATABASE_URL }); -exports.admin = admin; - -exports.createUser = async (req, res) => { +const createUser = async (req, res) => { logger.log("admin-create-user", "ADMIN", req.user.email, null, { request: req.body, ioadmin: true @@ -61,57 +59,45 @@ exports.createUser = async (req, res) => { } }; -exports.promanagerWelcomeEmail = (req, res) => { +const sendPromanagerWelcomeEmail = (req, res) => { const { authid, email } = req.body; - // Gate the operation to only admin users - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, { - request: req.body, - user: req.user - }); - res.sendStatus(404); - return; - } - + // Fetch user from Firebase admin .auth() .getUser(authid) .then((userRecord) => { if (!userRecord) { - res.status(404).json({ message: "User not found in Firebase." }); - return Promise.reject("User not found in Firebase."); + return Promise.reject({ status: 404, message: "User not found in Firebase." }); } // Fetch user data from the database using GraphQL return client.request( ` - query GET_USER_BY_EMAIL($email: String!) { - users(where: { email: { _eq: $email } }) { - email - validemail - associations { + query GET_USER_BY_EMAIL($email: String!) { + users(where: { email: { _eq: $email } }) { + email + validemail + associations { + id + shopid + bodyshop { id - shopid - bodyshop { - id - convenient_company - } + convenient_company } } } - `, + }`, { email: email.toLowerCase() } ); }) .then((dbUserResult) => { const dbUser = dbUserResult?.users?.[0]; if (!dbUser) { - res.status(404).json({ message: "User not found in database." }); - return Promise.reject("User not found in database."); + return Promise.reject({ status: 404, message: "User not found in database." }); } - // Check if the email is valid before proceeding + // Validate email before proceeding if (!dbUser.validemail) { logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, { message: "User email is not valid, skipping email.", @@ -120,11 +106,11 @@ exports.promanagerWelcomeEmail = (req, res) => { return res.status(200).json({ message: "User email is not valid, email not sent." }); } - // Check if convenient_company is equal to "promanager" + // Check if the user's company is ProManager const convenientCompany = dbUser.associations?.[0]?.bodyshop?.convenient_company; if (convenientCompany !== "promanager") { logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, { - message: `convenient_company is not "promanager", skipping email.`, + message: 'convenient_company is not "promanager", skipping email.', convenientCompany }); return res.status(200).json({ message: `convenient_company is not "promanager", email not sent.` }); @@ -134,13 +120,10 @@ exports.promanagerWelcomeEmail = (req, res) => { return admin .auth() .generatePasswordResetLink(dbUser.email) - .then((resetLink) => ({ - dbUser, - resetLink - })); + .then((resetLink) => ({ dbUser, resetLink })); }) .then(({ dbUser, resetLink }) => { - // Send email logic here (replace this with your email-sending service) + // Send welcome email (replace with your actual email-sending service) return sendProManagerWelcomeEmail({ to: dbUser.email, subject: "Welcome to the ProManager platform.", @@ -148,17 +131,18 @@ exports.promanagerWelcomeEmail = (req, res) => { header: "", subHeader: "", body: ` -

Welcome to the ProManager platform. Please click the link below to reset your password:

-

Reset your password

-

User Details:

-
    -
  • Email: ${dbUser.email}
  • -
- ` +

Welcome to the ProManager platform. Please click the link below to reset your password:

+

Reset your password

+

User Details:

+
    +
  • Email: ${dbUser.email}
  • +
+ ` }) }); }) .then(() => { + // Log success and return response logger.log("admin-send-welcome-email", "ADMIN", req.user.email, null, { request: req.body, ioadmin: true, @@ -167,30 +151,23 @@ exports.promanagerWelcomeEmail = (req, res) => { res.status(200).json({ message: "Welcome email sent successfully." }); }) .catch((error) => { - logger.log("admin-send-welcome-email-error", "ERROR", req.user.email, null, { - error - }); + logger.log("admin-send-welcome-email-error", "ERROR", req.user.email, null, { error }); + if (!res.headersSent) { - res.status(500).json({ message: "Error sending welcome email.", error }); + res.status(error.status || 500).json({ + message: error.message || "Error sending welcome email.", + error + }); } }); }; -exports.updateUser = (req, res) => { +const updateUser = (req, res) => { logger.log("admin-update-user", "ADMIN", req.user.email, null, { request: req.body, ioadmin: true }); - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, { - request: req.body, - user: req.user - }); - res.sendStatus(404); - return; - } - admin .auth() .updateUser( @@ -223,21 +200,12 @@ exports.updateUser = (req, res) => { }); }; -exports.getUser = (req, res) => { +const getUser = (req, res) => { logger.log("admin-get-user", "ADMIN", req.user.email, null, { request: req.body, ioadmin: true }); - if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) { - logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, { - request: req.body, - user: req.user - }); - res.sendStatus(404); - return; - } - admin .auth() .getUser(req.body.uid) @@ -252,7 +220,7 @@ exports.getUser = (req, res) => { }); }; -exports.sendNotification = async (req, res) => { +const sendNotification = async (req, res) => { setTimeout(() => { // Send a message to the device corresponding to the provided // registration token. @@ -285,7 +253,7 @@ exports.sendNotification = async (req, res) => { }, 500); }; -exports.subscribe = async (req, res) => { +const subscribe = async (req, res) => { const result = await admin .messaging() .subscribeToTopic(req.body.fcm_tokens, `${req.body.imexshopid}-${req.body.type}`); @@ -293,7 +261,7 @@ exports.subscribe = async (req, res) => { res.json(result); }; -exports.unsubscribe = async (req, res) => { +const unsubscribe = async (req, res) => { try { const result = await admin .messaging() @@ -305,6 +273,17 @@ exports.unsubscribe = async (req, res) => { } }; +module.exports = { + admin, + createUser, + updateUser, + getUser, + sendPromanagerWelcomeEmail, + sendNotification, + subscribe, + unsubscribe +}; + //Admin claims code. // const uid = "JEqqYlsadwPEXIiyRBR55fflfko1"; diff --git a/server/routes/adminRoutes.js b/server/routes/adminRoutes.js index 74775724d..ac0ebb6fb 100644 --- a/server/routes/adminRoutes.js +++ b/server/routes/adminRoutes.js @@ -1,19 +1,20 @@ const express = require("express"); const router = express.Router(); -const fb = require("../firebase/firebase-handler"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const { createAssociation, createShop, updateShop, updateCounter } = require("../admin/adminops"); +const { updateUser, getUser, createUser, sendPromanagerWelcomeEmail } = require("../firebase/firebase-handler"); const validateAdminMiddleware = require("../middleware/validateAdminMiddleware"); router.use(validateFirebaseIdTokenMiddleware); +router.use(validateAdminMiddleware); -router.post("/createassociation", validateAdminMiddleware, createAssociation); -router.post("/createshop", validateAdminMiddleware, createShop); -router.post("/updateshop", validateAdminMiddleware, updateShop); -router.post("/updatecounter", validateAdminMiddleware, updateCounter); -router.post("/updateuser", fb.updateUser); -router.post("/getuser", fb.getUser); -router.post("/createuser", fb.createUser); -router.post("/promanagerwelcome", fb.promanagerWelcomeEmail); +router.post("/createassociation", createAssociation); +router.post("/createshop", createShop); +router.post("/updateshop", updateShop); +router.post("/updatecounter", updateCounter); +router.post("/updateuser", updateUser); +router.post("/getuser", getUser); +router.post("/createuser", createUser); +router.post("/promanagerwelcome", sendPromanagerWelcomeEmail); module.exports = router; From 145cf7cc9367c9322c333b0dd897525579b6762f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 19 Sep 2024 12:54:26 -0400 Subject: [PATCH 3/4] IO-2782-Send-Promanager-Welcome-Email - Finalize cleanup Signed-off-by: Dave Richer --- server/firebase/firebase-handler.js | 33 ++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/server/firebase/firebase-handler.js b/server/firebase/firebase-handler.js index 7223d89a8..e47eeffc1 100644 --- a/server/firebase/firebase-handler.js +++ b/server/firebase/firebase-handler.js @@ -7,9 +7,7 @@ const admin = require("firebase-admin"); const logger = require("../utils/logger"); const { sendProManagerWelcomeEmail } = require("../email/sendemail"); const client = require("../graphql-client/graphql-client").client; - const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON); -const adminEmail = require("../utils/adminEmail"); const generateEmailTemplate = require("../email/generateTemplate"); admin.initializeApp({ @@ -210,7 +208,36 @@ const getUser = (req, res) => { .auth() .getUser(req.body.uid) .then((userRecord) => { - res.json(userRecord); + return client + .request( + ` + query GET_USER_BY_AUTHID($authid: String!) { + users(where: { authid: { _eq: $authid } }) { + email + displayName + validemail + associations { + id + shopid + bodyshop { + id + convenient_company + } + } + } + } + `, + { authid: req.body.uid } + ) + .then((dbUserResult) => { + res.json({ + ...userRecord, + db: { + validemail: dbUserResult?.users?.[0]?.validemail, + company: dbUserResult?.users?.[0]?.associations?.[0]?.bodyshop?.convenient_company + } + }); + }); }) .catch((error) => { logger.log("admin-get-user-error", "ERROR", req.user.email, null, { From 4ad87a522c0ab2b5e4ae78c0927b4d393ad1dfff Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 19 Sep 2024 13:08:23 -0400 Subject: [PATCH 4/4] IO-2782-Send-Promanager-Welcome-Email - Update for merge conflict Signed-off-by: Dave Richer --- server/email/sendemail.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/email/sendemail.js b/server/email/sendemail.js index 878918f48..65b5a60ce 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -110,7 +110,7 @@ const sendProManagerWelcomeEmail = async (to, subject, html) => { } }; -const sendTaskEmail = async ({ to, subject, text, attachments }) => { +const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => { try { transporter.sendMail( { @@ -121,7 +121,7 @@ const sendTaskEmail = async ({ to, subject, text, attachments }) => { }), to: to, subject: subject, - text: text, + ...(type === "text" ? { text } : { html }), attachments: attachments || null }, (err, info) => {