const EmailClient = require("../core/EmailClient"); const TemplateManager = require("../core/TemplateManager"); const logger = require("../../../utils/logger"); /** * ForumEmailService handles all forum-related email notifications * This service is responsible for: * - Sending comment notifications to post authors * - Sending reply notifications to comment authors * - Sending answer accepted notifications * - Sending thread activity notifications to participants * - Sending location-based item request notifications to nearby users * - Sending post/comment deletion notifications to authors */ class ForumEmailService { constructor() { this.emailClient = new EmailClient(); this.templateManager = new TemplateManager(); this.initialized = false; } /** * Initialize the forum email service * @returns {Promise} */ async initialize() { if (this.initialized) return; await Promise.all([ this.emailClient.initialize(), this.templateManager.initialize(), ]); this.initialized = true; logger.info("Forum Email Service initialized successfully"); } /** * Send notification when someone comments on a post * @param {Object} postAuthor - Post author user object * @param {string} postAuthor.firstName - Post author's first name * @param {string} postAuthor.email - Post author's email * @param {Object} commenter - Commenter user object * @param {string} commenter.firstName - Commenter's first name * @param {string} commenter.lastName - Commenter's last name * @param {Object} post - Forum post object * @param {number} post.id - Post ID * @param {string} post.title - Post title * @param {Object} comment - Comment object * @param {string} comment.content - Comment content * @param {Date} comment.createdAt - Comment creation timestamp * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumCommentNotification(postAuthor, commenter, post, comment) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const timestamp = new Date(comment.createdAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }); const variables = { postAuthorName: postAuthor.firstName || "there", commenterName: `${commenter.firstName} ${commenter.lastName}`.trim() || "Someone", postTitle: post.title, commentContent: comment.content, postUrl: postUrl, timestamp: timestamp, }; const htmlContent = await this.templateManager.renderTemplate( "forumCommentToPostAuthor", variables, ); const subject = `${commenter.firstName} ${commenter.lastName} commented on your post`; const result = await this.emailClient.sendEmail( postAuthor.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum comment notification email sent to ${postAuthor.email}`, ); } return result; } catch (error) { logger.error("Failed to send forum comment notification email:", error); return { success: false, error: error.message }; } } /** * Send notification when someone replies to a comment * @param {Object} commentAuthor - Original comment author user object * @param {string} commentAuthor.firstName - Comment author's first name * @param {string} commentAuthor.email - Comment author's email * @param {Object} replier - Replier user object * @param {string} replier.firstName - Replier's first name * @param {string} replier.lastName - Replier's last name * @param {Object} post - Forum post object * @param {number} post.id - Post ID * @param {string} post.title - Post title * @param {Object} reply - Reply comment object * @param {string} reply.content - Reply content * @param {Date} reply.createdAt - Reply creation timestamp * @param {Object} parentComment - Parent comment being replied to * @param {string} parentComment.content - Parent comment content * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumReplyNotification( commentAuthor, replier, post, reply, parentComment, ) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const timestamp = new Date(reply.createdAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }); const variables = { commentAuthorName: commentAuthor.firstName || "there", replierName: `${replier.firstName} ${replier.lastName}`.trim() || "Someone", postTitle: post.title, parentCommentContent: parentComment.content, replyContent: reply.content, postUrl: postUrl, timestamp: timestamp, }; const htmlContent = await this.templateManager.renderTemplate( "forumReplyToCommentAuthor", variables, ); const subject = `${replier.firstName} ${replier.lastName} replied to your comment`; const result = await this.emailClient.sendEmail( commentAuthor.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum reply notification email sent to ${commentAuthor.email}`, ); } return result; } catch (error) { logger.error("Failed to send forum reply notification email:", error); return { success: false, error: error.message }; } } /** * Send notification when a comment is marked as the accepted answer * @param {Object} commentAuthor - Comment author user object * @param {string} commentAuthor.firstName - Comment author's first name * @param {string} commentAuthor.email - Comment author's email * @param {Object} postAuthor - Post author user object who accepted the answer * @param {string} postAuthor.firstName - Post author's first name * @param {string} postAuthor.lastName - Post author's last name * @param {Object} post - Forum post object * @param {number} post.id - Post ID * @param {string} post.title - Post title * @param {Object} comment - Comment that was accepted as answer * @param {string} comment.content - Comment content * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumAnswerAcceptedNotification( commentAuthor, postAuthor, post, comment, ) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const variables = { commentAuthorName: commentAuthor.firstName || "there", postAuthorName: `${postAuthor.firstName} ${postAuthor.lastName}`.trim() || "Someone", postTitle: post.title, commentContent: comment.content, postUrl: postUrl, }; const htmlContent = await this.templateManager.renderTemplate( "forumAnswerAcceptedToCommentAuthor", variables, ); const subject = `Your comment was marked as the accepted answer!`; const result = await this.emailClient.sendEmail( commentAuthor.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum answer accepted notification email sent to ${commentAuthor.email}`, ); } return result; } catch (error) { logger.error( "Failed to send forum answer accepted notification email:", error, ); return { success: false, error: error.message }; } } /** * Send notification to thread participants about new activity * @param {Object} participant - Participant user object * @param {string} participant.firstName - Participant's first name * @param {string} participant.email - Participant's email * @param {Object} commenter - User who posted new comment * @param {string} commenter.firstName - Commenter's first name * @param {string} commenter.lastName - Commenter's last name * @param {Object} post - Forum post object * @param {number} post.id - Post ID * @param {string} post.title - Post title * @param {Object} comment - New comment/activity * @param {string} comment.content - Comment content * @param {Date} comment.createdAt - Comment creation timestamp * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumThreadActivityNotification( participant, commenter, post, comment, ) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const timestamp = new Date(comment.createdAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }); const variables = { participantName: participant.firstName || "there", commenterName: `${commenter.firstName} ${commenter.lastName}`.trim() || "Someone", postTitle: post.title, commentContent: comment.content, postUrl: postUrl, timestamp: timestamp, }; const htmlContent = await this.templateManager.renderTemplate( "forumThreadActivityToParticipant", variables, ); const subject = `New activity on a post you're following`; const result = await this.emailClient.sendEmail( participant.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum thread activity notification email sent to ${participant.email}`, ); } return result; } catch (error) { logger.error( "Failed to send forum thread activity notification email:", error, ); return { success: false, error: error.message }; } } /** * Send notification when a discussion is closed * @param {Object} recipient - Recipient user object * @param {string} recipient.firstName - Recipient's first name * @param {string} recipient.email - Recipient's email * @param {Object} closer - User who closed the discussion (can be admin or post author) * @param {string} closer.firstName - Closer's first name * @param {string} closer.lastName - Closer's last name * @param {Object} post - Forum post object * @param {number} post.id - Post ID * @param {string} post.title - Post title * @param {Date} closedAt - Timestamp when discussion was closed * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumPostClosedNotification(recipient, closer, post, closedAt) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const timestamp = new Date(closedAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }); const variables = { recipientName: recipient.firstName || "there", adminName: `${closer.firstName} ${closer.lastName}`.trim() || "A user", postTitle: post.title, postUrl: postUrl, timestamp: timestamp, }; const htmlContent = await this.templateManager.renderTemplate( "forumPostClosed", variables, ); const subject = `Discussion closed: ${post.title}`; const result = await this.emailClient.sendEmail( recipient.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum post closed notification email sent to ${recipient.email}`, ); } return result; } catch (error) { logger.error( "Failed to send forum post closed notification email:", error, ); return { success: false, error: error.message }; } } /** * Send notification when a forum post is deleted by admin * @param {Object} postAuthor - Post author user object * @param {string} postAuthor.firstName - Post author's first name * @param {string} postAuthor.email - Post author's email * @param {Object} admin - Admin user object who deleted the post * @param {string} admin.firstName - Admin's first name * @param {string} admin.lastName - Admin's last name * @param {Object} post - Forum post object * @param {string} post.title - Post title * @param {string} deletionReason - Reason for deletion * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumPostDeletionNotification( postAuthor, admin, post, deletionReason, ) { if (!this.initialized) { await this.initialize(); } try { const supportEmail = process.env.CUSTOMER_SUPPORT_EMAIL; const frontendUrl = process.env.FRONTEND_URL; const variables = { postAuthorName: postAuthor.firstName || "there", adminName: `${admin.firstName} ${admin.lastName}`.trim() || "An administrator", postTitle: post.title, deletionReason, supportEmail, forumUrl: `${frontendUrl}/forum`, }; const htmlContent = await this.templateManager.renderTemplate( "forumPostDeletionToAuthor", variables, ); const subject = `Important: Your forum post "${post.title}" has been removed`; const result = await this.emailClient.sendEmail( postAuthor.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum post deletion notification email sent to ${postAuthor.email}`, ); } return result; } catch (error) { logger.error( "Failed to send forum post deletion notification email:", error, ); return { success: false, error: error.message }; } } /** * Send notification when a forum comment is deleted by admin * @param {Object} commentAuthor - Comment author user object * @param {string} commentAuthor.firstName - Comment author's first name * @param {string} commentAuthor.email - Comment author's email * @param {Object} admin - Admin user object who deleted the comment * @param {string} admin.firstName - Admin's first name * @param {string} admin.lastName - Admin's last name * @param {Object} post - Forum post object the comment belongs to * @param {string} post.title - Post title * @param {string} post.id - Post ID * @param {string} deletionReason - Reason for deletion * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendForumCommentDeletionNotification( commentAuthor, admin, post, deletionReason, ) { if (!this.initialized) { await this.initialize(); } try { const supportEmail = process.env.CUSTOMER_SUPPORT_EMAIL; const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const variables = { commentAuthorName: commentAuthor.firstName || "there", adminName: `${admin.firstName} ${admin.lastName}`.trim() || "An administrator", postTitle: post.title, postUrl, deletionReason, supportEmail, }; const htmlContent = await this.templateManager.renderTemplate( "forumCommentDeletionToAuthor", variables, ); const subject = `Your comment on "${post.title}" has been removed`; const result = await this.emailClient.sendEmail( commentAuthor.email, subject, htmlContent, ); if (result.success) { logger.info( `Forum comment deletion notification email sent to ${commentAuthor.email}`, ); } return result; } catch (error) { logger.error( "Failed to send forum comment deletion notification email:", error, ); return { success: false, error: error.message }; } } /** * Send notification to nearby users about an item request * @param {Object} recipient - Recipient user object * @param {string} recipient.firstName - Recipient's first name * @param {string} recipient.email - Recipient's email * @param {Object} requester - User who posted the item request * @param {string} requester.firstName - Requester's first name * @param {string} requester.lastName - Requester's last name * @param {Object} post - Forum post object (item request) * @param {number} post.id - Post ID * @param {string} post.title - Item being requested * @param {string} post.content - Request description * @param {string|number} distance - Distance from recipient to request location (in miles) * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendItemRequestNotification(recipient, requester, post, distance) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL; const postUrl = `${frontendUrl}/forum/posts/${post.id}`; const variables = { recipientName: recipient.firstName || "there", requesterName: `${requester.firstName} ${requester.lastName}`.trim() || "Someone", itemRequested: post.title, requestDescription: post.content, postUrl: postUrl, distance: distance, }; const htmlContent = await this.templateManager.renderTemplate( "forumItemRequestNotification", variables, ); const subject = `Someone nearby is looking for: ${post.title}`; const result = await this.emailClient.sendEmail( recipient.email, subject, htmlContent, ); if (result.success) { logger.info( `Item request notification email sent to ${recipient.email}`, ); } return result; } catch (error) { logger.error("Failed to send item request notification email:", error); return { success: false, error: error.message }; } } } module.exports = ForumEmailService;