From db4155ebfd0667a8ab6eee7962778fbab8dbd9af Mon Sep 17 00:00:00 2001 From: Akshat Mittal Date: Mon, 4 Sep 2023 17:36:24 +0530 Subject: [PATCH] feat: created email service for several operations --- controllers/participation.ts | 46 ++++++- controllers/team.ts | 13 ++ utils/emails.ts | 258 ++++++++++++++++++++++++++++++++--- 3 files changed, 296 insertions(+), 21 deletions(-) diff --git a/controllers/participation.ts b/controllers/participation.ts index 7915892..5cb6467 100644 --- a/controllers/participation.ts +++ b/controllers/participation.ts @@ -8,6 +8,13 @@ import Participant from "@/models/Participant"; import Team from "@/models/Team"; import User from "@/models/User"; import { ApiRequest, ApiResponse } from "@/types/api"; +import { + sendAcceptedInTeam, + sendRegistrationConfirmation, + sendRejectedInTeam, + sendRequestedInTeam, + sendWithdrawnFromEvent, +} from "@/utils/emails"; import mongoose from "mongoose"; export const getAllParticipants = async (req: ApiRequest, res: ApiResponse) => { @@ -241,6 +248,10 @@ export const participateInEvent = async (req: ApiRequest, res: ApiResponse) => { path: "user", select: "name email avatar", }); + await sendRegistrationConfirmation( + newParticipant.user.email, + newParticipant.event.name + ); return res.status(201).json({ message: RESPONSE_MESSAGES.SUCCESS, data: newParticipant, @@ -267,6 +278,15 @@ export const participateInEvent = async (req: ApiRequest, res: ApiResponse) => { user: req.user?.id, status: TEAM_PARTICIPATION_STATUS.PENDING, }); + const foundTeamLeader = await User.findById( + foundTeam.createdBy.toString() + ); + await sendRequestedInTeam( + foundTeamLeader.email, + foundEvent.name, + foundTeamLeader.name, + foundTeam.name + ); await newParticipant.populate({ path: "event", select: "name description", @@ -335,16 +355,30 @@ export const handleParticipantStatusInTeam = async ( message: "Please select a valid status to approve or reject", }); } + const foundUser = await User.findById(foundParticipant.user.toString()); + const foundEvent = await Event.findById( + foundParticipant.event.toString() + ); if (req.body.status === TEAM_PARTICIPATION_STATUS.ACCEPTED) { foundParticipant.status = TEAM_PARTICIPATION_STATUS.ACCEPTED; + await foundParticipant.save(); + await sendAcceptedInTeam( + foundUser?.email, + foundEvent.name, + foundTeam.name + ); } else if (req.body.status === TEAM_PARTICIPATION_STATUS.REJECTED) { await Participant.findByIdAndDelete(foundParticipant._id); + await sendRejectedInTeam( + foundUser?.email, + foundEvent.name, + foundTeam.name + ); } else { return res.status(400).json({ message: "Please select a valid status to approve", }); } - await foundParticipant.save(); return res.status(200).json({ message: RESPONSE_MESSAGES.SUCCESS, data: foundParticipant, @@ -424,7 +458,15 @@ export const approveParticipant = async (req: ApiRequest, res: ApiResponse) => { }); } foundParticipant.status = TEAM_PARTICIPATION_STATUS.ACCEPTED; + const foundUser = await User.findById( + foundParticipant.user.toString() + ); await foundParticipant.save(); + await sendAcceptedInTeam( + foundUser?.email, + foundEvent.name, + foundTeam.name + ); return res.status(200).json({ message: RESPONSE_MESSAGES.SUCCESS, data: foundParticipant, @@ -486,6 +528,8 @@ export const removeParticipantFromEvent = async ( } } await Participant.findByIdAndDelete(foundParticipant._id); + const foundUser = await User.findById(foundParticipant.user.toString()); + await sendWithdrawnFromEvent(foundUser?.email, foundEvent.name); return res.status(204).json({ message: RESPONSE_MESSAGES.SUCCESS, data: foundParticipant, diff --git a/controllers/team.ts b/controllers/team.ts index 052fc25..2bae477 100644 --- a/controllers/team.ts +++ b/controllers/team.ts @@ -8,6 +8,7 @@ import Event from "@/models/Event"; import Participant from "@/models/Participant"; import Team from "@/models/Team"; import User from "@/models/User"; +import { sendRegistrationConfirmation, sendTeamRemoved } from "@/utils/emails"; export const getAllTeams = async (req: ApiRequest, res: ApiResponse) => { try { @@ -97,6 +98,12 @@ export const createTeam = async (req: ApiRequest, res: ApiResponse) => { user: req.user?.id, status: TEAM_PARTICIPATION_STATUS.ACCEPTED, }); + const foundUser = await User.findById(req.user?.id); + await sendRegistrationConfirmation( + foundUser?.email, + foundEvent.name, + req.body.name + ); await newTeam.populate({ path: "createdBy", select: "name email avatar", @@ -139,8 +146,14 @@ export const removeTeam = async (req: ApiRequest, res: ApiResponse) => { message: "Only the creator can delete this team", }); } + const foundEvent = await Event.findById(foundTeam.event.toString()); await Participant.deleteMany({ team: teamId }); await Team.findByIdAndDelete(teamId); + await sendTeamRemoved( + foundUser?.email, + foundEvent?.name, + foundTeam.name + ); return res.status(204).json({ message: RESPONSE_MESSAGES.SUCCESS }); } catch (error: any) { console.error(error); diff --git a/utils/emails.ts b/utils/emails.ts index d93ee45..0aeec64 100644 --- a/utils/emails.ts +++ b/utils/emails.ts @@ -1,9 +1,16 @@ +/* eslint-disable no-mixed-spaces-and-tabs */ +import { frontendBaseUrl } from "@/constants/variables"; import { sendEmail } from "@/services/emails"; -export const getEmailTemplate = (title: string, otp: string) => ` - - - +export const getEmailTemplateHTML = ( + title: string = "", + subtitle: string = "", + footnote: string = "", + btnText: string = "", + btnLink: string = "" +) => { + let template = ` + + + +
+
+
+ + + + + + +
+ + + +
+ +
+
+
+ + + + + + ` + : "" +} +${ + footnote + ? `
@@ -283,12 +339,14 @@ table, td { color: #000000; } #u_body a { color: #0000ee; text-decoration: under
+` + : "" +} - - -
+ +
@@ -331,17 +389,91 @@ table, td { color: #000000; } #u_body a { color: #0000ee; text-decoration: under - - + `; + return template; +}; + +type EMAIL_TEMPLATE = + | "REGISTER" + | "FORGOT_PASSWORD" + | "REGISTER_IN_EVENT" + | "ACCEPTED_IN_TEAM" + | "REQUESTED_TEAM_JOIN" + | "REJECTED_IN_TEAM" + | "TEAM_REMOVED" + | "WITHDRAWN_FROM_EVENT"; + +export const getEmailTemplate = (type: EMAIL_TEMPLATE, ...args: any[]) => { + let title = ""; + let subtitle = ""; + let footnote = ""; + let btnText = ""; + let btnLink = ""; + + switch (type) { + case "REGISTER": + title = "Verify your email"; + subtitle = `Your OTP is ${args[0]}`; + footnote = + "Do not share this OTP with anyone. If you did not request this email, please ignore it."; + break; + case "FORGOT_PASSWORD": + title = "Meraki - Reset your password"; + subtitle = `Your OTP is ${args[0]}`; + footnote = + "Do not share this OTP with anyone. If you did not request this email, please ignore it."; + break; + case "REGISTER_IN_EVENT": + title = `Meraki - You have registered for ${args[0]}`; + subtitle = `You have registered for ${args[0]}${ + args[1] ? ` in team ${args[1]}` : "" + }`; + btnText = "Explore other Events"; + btnLink = `${frontendBaseUrl}/events`; + break; + case "ACCEPTED_IN_TEAM": + title = `Meraki - You have been accepted in ${args[0]}`; + subtitle = `You have been accepted in ${args[0]} in team ${args[1]}`; + btnText = "Explore other Events"; + btnLink = `${frontendBaseUrl}/events`; + break; + case "REQUESTED_TEAM_JOIN": + title = `Meraki - You have a new request in ${args[0]}`; + subtitle = `You have a new request in ${args[0]} from ${args[1]} in team ${args[2]}`; + btnText = "Check your requests"; + btnLink = `${frontendBaseUrl}/profile`; + break; + case "REJECTED_IN_TEAM": + title = `Meraki - Your request has been rejected in ${args[0]}`; + subtitle = `Your request has been rejected in ${args[0]} in team ${args[1]}`; + btnText = "Explore other Events"; + btnLink = `${frontendBaseUrl}/events`; + break; + case "TEAM_REMOVED": + title = `Meraki - Team ${args[1]} removed from ${args[0]}`; + subtitle = `Team ${args[1]} has been removed from ${args[0]}`; + btnText = "Explore other Events"; + btnLink = `${frontendBaseUrl}/events`; + break; + case "WITHDRAWN_FROM_EVENT": + title = `Meraki - Withdrawn from ${args[0]}`; + subtitle = `You have been withdrawn from ${args[0]}`; + btnText = "Explore other Events"; + btnLink = `${frontendBaseUrl}/events`; + break; + } + + return getEmailTemplateHTML(title, subtitle, footnote, btnText, btnLink); +}; export const sendRegistrationOtp = async (to: string, otp: string) => { try { const title = "Welcome to Meraki - Annual Techfest of IIIT Una. Verify your email"; - const html = getEmailTemplate("Verify your email", otp); + const html = getEmailTemplate("REGISTER", otp); await sendEmail(to, title, html); } catch (error: any) { console.error(error); @@ -351,7 +483,93 @@ export const sendRegistrationOtp = async (to: string, otp: string) => { export const sendPasswordResetOtp = async (to: string, otp: string) => { try { const title = "Reset your password"; - const html = getEmailTemplate(title, otp); + const html = getEmailTemplate("FORGOT_PASSWORD", otp); + await sendEmail(to, title, html); + } catch (error: any) { + console.error(error); + } +}; + +export const sendRegistrationConfirmation = async ( + to: string, + eventName: string, + teamName?: string +) => { + try { + const title = `You have registered for ${eventName}`; + const html = getEmailTemplate("REGISTER_IN_EVENT", eventName, teamName); + await sendEmail(to, title, html); + } catch (error: any) { + console.error(error); + } +}; + +export const sendRequestedInTeam = async ( + to: string, + eventName: string, + leaderName: string, + teamName: string +) => { + try { + const title = `You have a new request in ${eventName}`; + const html = getEmailTemplate( + "REQUESTED_TEAM_JOIN", + eventName, + leaderName, + teamName + ); + await sendEmail(to, title, html); + } catch (error: any) { + console.error(error); + } +}; + +export const sendAcceptedInTeam = async ( + to: string, + eventName: string, + teamName: string +) => { + try { + const title = `You have been accepted in ${eventName}`; + const html = getEmailTemplate("ACCEPTED_IN_TEAM", eventName, teamName); + await sendEmail(to, title, html); + } catch (error: any) { + console.error(error); + } +}; + +export const sendRejectedInTeam = async ( + to: string, + eventName: string, + teamName: string +) => { + try { + const title = `Your request has been rejected in ${eventName}`; + const html = getEmailTemplate("REJECTED_IN_TEAM", eventName, teamName); + await sendEmail(to, title, html); + } catch (error: any) { + console.error(error); + } +}; + +export const sendTeamRemoved = async ( + to: string, + eventName: string, + teamName: string +) => { + try { + const title = `Team ${teamName} removed from ${eventName}`; + const html = getEmailTemplate("TEAM_REMOVED", eventName, teamName); + await sendEmail(to, title, html); + } catch (error: any) { + console.error(error); + } +}; + +export const sendWithdrawnFromEvent = async (to: string, eventName: string) => { + try { + const title = `You have withdrawn from ${eventName}`; + const html = getEmailTemplate("WITHDRAWN_FROM_EVENT", eventName); await sendEmail(to, title, html); } catch (error: any) { console.error(error);