575 lines
18 KiB
JavaScript
575 lines
18 KiB
JavaScript
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<void>}
|
|
*/
|
|
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 || "http://localhost:3000";
|
|
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 || "http://localhost:3000";
|
|
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 || "http://localhost:3000";
|
|
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 || "http://localhost:3000";
|
|
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 || "http://localhost:3000";
|
|
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.SUPPORT_EMAIL;
|
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
|
|
|
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.SUPPORT_EMAIL;
|
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
|
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 || "http://localhost:3000";
|
|
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;
|