integrated email is forum posts/comments
This commit is contained in:
@@ -55,6 +55,10 @@ class EmailService {
|
||||
"feedbackConfirmationToUser.html",
|
||||
"feedbackNotificationToAdmin.html",
|
||||
"newMessageToUser.html",
|
||||
"forumCommentToPostAuthor.html",
|
||||
"forumReplyToCommentAuthor.html",
|
||||
"forumAnswerAcceptedToCommentAuthor.html",
|
||||
"forumThreadActivityToParticipant.html",
|
||||
];
|
||||
|
||||
for (const templateFile of templateFiles) {
|
||||
@@ -65,14 +69,23 @@ class EmailService {
|
||||
this.templates.set(templateName, templateContent);
|
||||
console.log(`✓ Loaded template: ${templateName}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed to load template ${templateFile}:`, error.message);
|
||||
console.error(` Template path: ${path.join(templatesDir, templateFile)}`);
|
||||
console.error(
|
||||
`✗ Failed to load template ${templateFile}:`,
|
||||
error.message
|
||||
);
|
||||
console.error(
|
||||
` Template path: ${path.join(templatesDir, templateFile)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Loaded ${this.templates.size} of ${templateFiles.length} email templates`);
|
||||
console.log(
|
||||
`Loaded ${this.templates.size} of ${templateFiles.length} email templates`
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn("Templates directory not found, using fallback templates");
|
||||
console.error("Failed to load email templates:", error);
|
||||
console.error("Templates directory:", templatesDir);
|
||||
console.error("Error stack:", error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,19 +191,39 @@ class EmailService {
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(templateName, variables = {}) {
|
||||
async renderTemplate(templateName, variables = {}) {
|
||||
// Ensure service is initialized before rendering
|
||||
if (!this.initialized) {
|
||||
console.log(`Email service not initialized yet, initializing now...`);
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
let template = this.templates.get(templateName);
|
||||
|
||||
if (!template) {
|
||||
console.error(`Template not found: ${templateName}`);
|
||||
console.error(
|
||||
`Available templates: ${Array.from(this.templates.keys()).join(", ")}`
|
||||
);
|
||||
console.error(`Stack trace:`, new Error().stack);
|
||||
console.log(`Using fallback template for: ${templateName}`);
|
||||
template = this.getFallbackTemplate(templateName);
|
||||
} else {
|
||||
console.log(`✓ Template found: ${templateName}`);
|
||||
}
|
||||
|
||||
let rendered = template;
|
||||
|
||||
Object.keys(variables).forEach((key) => {
|
||||
const regex = new RegExp(`{{${key}}}`, "g");
|
||||
rendered = rendered.replace(regex, variables[key] || "");
|
||||
});
|
||||
try {
|
||||
Object.keys(variables).forEach((key) => {
|
||||
const regex = new RegExp(`{{${key}}}`, "g");
|
||||
rendered = rendered.replace(regex, variables[key] || "");
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error rendering template ${templateName}:`, error);
|
||||
console.error(`Stack trace:`, error.stack);
|
||||
console.error(`Variables provided:`, Object.keys(variables));
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
@@ -504,7 +537,7 @@ class EmailService {
|
||||
: "Not specified",
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"conditionCheckReminderToUser",
|
||||
variables
|
||||
);
|
||||
@@ -543,25 +576,26 @@ class EmailService {
|
||||
let paymentSection = "";
|
||||
if (isRenter) {
|
||||
const totalAmount = parseFloat(rental.totalAmount) || 0;
|
||||
const isPaidRental = totalAmount > 0 && rental.paymentStatus === 'paid';
|
||||
const isPaidRental = totalAmount > 0 && rental.paymentStatus === "paid";
|
||||
|
||||
if (isPaidRental) {
|
||||
// Format payment method display
|
||||
let paymentMethodDisplay = "Payment method on file";
|
||||
if (rental.paymentMethodBrand && rental.paymentMethodLast4) {
|
||||
const brandCapitalized = rental.paymentMethodBrand.charAt(0).toUpperCase() +
|
||||
rental.paymentMethodBrand.slice(1);
|
||||
const brandCapitalized =
|
||||
rental.paymentMethodBrand.charAt(0).toUpperCase() +
|
||||
rental.paymentMethodBrand.slice(1);
|
||||
paymentMethodDisplay = `${brandCapitalized} ending in ${rental.paymentMethodLast4}`;
|
||||
}
|
||||
|
||||
const chargedAtFormatted = rental.chargedAt
|
||||
? new Date(rental.chargedAt).toLocaleString("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short"
|
||||
timeStyle: "short",
|
||||
})
|
||||
: new Date().toLocaleString("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short"
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
// Build payment receipt section HTML
|
||||
@@ -583,7 +617,9 @@ class EmailService {
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction ID</th>
|
||||
<td style="font-family: monospace; font-size: 12px;">${rental.stripePaymentIntentId || "N/A"}</td>
|
||||
<td style="font-family: monospace; font-size: 12px;">${
|
||||
rental.stripePaymentIntentId || "N/A"
|
||||
}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Transaction Date</th>
|
||||
@@ -606,7 +642,10 @@ class EmailService {
|
||||
|
||||
variables.paymentSection = paymentSection;
|
||||
|
||||
const htmlContent = this.renderTemplate("rentalConfirmationToUser", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"rentalConfirmationToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
// Use clear, transactional subject line with item name
|
||||
const subject = `Rental Confirmation - ${itemName}`;
|
||||
@@ -623,7 +662,10 @@ class EmailService {
|
||||
verificationUrl: verificationUrl,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("emailVerificationToUser", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"emailVerificationToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.sendEmail(
|
||||
user.email,
|
||||
@@ -633,11 +675,6 @@ class EmailService {
|
||||
}
|
||||
|
||||
async sendAlphaInvitation(email, code) {
|
||||
// Ensure service is initialized before rendering template
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||
|
||||
const variables = {
|
||||
@@ -648,7 +685,10 @@ class EmailService {
|
||||
message: `You've been invited to join our exclusive alpha testing program. Use the code <strong>${code}</strong> to unlock access and be among the first to experience our platform.`,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("alphaInvitationToUser", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"alphaInvitationToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.sendEmail(
|
||||
email,
|
||||
@@ -666,7 +706,10 @@ class EmailService {
|
||||
resetUrl: resetUrl,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("passwordResetToUser", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"passwordResetToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.sendEmail(
|
||||
user.email,
|
||||
@@ -687,7 +730,10 @@ class EmailService {
|
||||
timestamp: timestamp,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("passwordChangedToUser", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"passwordChangedToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.sendEmail(
|
||||
user.email,
|
||||
@@ -744,7 +790,10 @@ class EmailService {
|
||||
approveUrl: approveUrl,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("rentalRequestToOwner", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"rentalRequestToOwner",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.sendEmail(
|
||||
owner.email,
|
||||
@@ -797,7 +846,7 @@ class EmailService {
|
||||
viewRentalsUrl: viewRentalsUrl,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"rentalRequestConfirmationToRenter",
|
||||
variables
|
||||
);
|
||||
@@ -819,9 +868,7 @@ class EmailService {
|
||||
});
|
||||
|
||||
if (!renter) {
|
||||
console.error(
|
||||
"Renter not found for rental decline notification"
|
||||
);
|
||||
console.error("Renter not found for rental decline notification");
|
||||
return { success: false, error: "Renter not found" };
|
||||
}
|
||||
|
||||
@@ -867,7 +914,7 @@ class EmailService {
|
||||
totalAmount: totalAmount.toFixed(2),
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"rentalDeclinedToRenter",
|
||||
variables
|
||||
);
|
||||
@@ -920,11 +967,16 @@ class EmailService {
|
||||
earningsDashboardUrl: earningsDashboardUrl,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("payoutReceivedToOwner", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"payoutReceivedToOwner",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.sendEmail(
|
||||
owner.email,
|
||||
`Earnings Received - $${payoutAmount.toFixed(2)} for ${rental.item?.name || "Your Item"}`,
|
||||
`Earnings Received - $${payoutAmount.toFixed(2)} for ${
|
||||
rental.item?.name || "Your Item"
|
||||
}`,
|
||||
htmlContent
|
||||
);
|
||||
}
|
||||
@@ -994,7 +1046,9 @@ class EmailService {
|
||||
additionalInfo = `
|
||||
<div class="info-box">
|
||||
<p><strong>Full Refund Processed</strong></p>
|
||||
<p>You will receive a full refund of $${refundInfo.amount.toFixed(2)}. The refund will appear in your account within 5-10 business days.</p>
|
||||
<p>You will receive a full refund of $${refundInfo.amount.toFixed(
|
||||
2
|
||||
)}. The refund will appear in your account within 5-10 business days.</p>
|
||||
</div>
|
||||
<div style="text-align: center">
|
||||
<a href="${browseUrl}" class="button">Browse Other Items</a>
|
||||
@@ -1035,7 +1089,9 @@ class EmailService {
|
||||
<h2>Refund Information</h2>
|
||||
<div class="refund-amount">$${refundInfo.amount.toFixed(2)}</div>
|
||||
<div class="info-box">
|
||||
<p><strong>Refund Amount:</strong> $${refundInfo.amount.toFixed(2)} (${refundPercentage}% of total)</p>
|
||||
<p><strong>Refund Amount:</strong> $${refundInfo.amount.toFixed(
|
||||
2
|
||||
)} (${refundPercentage}% of total)</p>
|
||||
<p><strong>Reason:</strong> ${refundInfo.reason}</p>
|
||||
<p><strong>Processing Time:</strong> Refunds typically appear within 5-10 business days.</p>
|
||||
</div>
|
||||
@@ -1063,7 +1119,7 @@ class EmailService {
|
||||
refundSection: refundSection,
|
||||
};
|
||||
|
||||
const confirmationHtml = this.renderTemplate(
|
||||
const confirmationHtml = await this.renderTemplate(
|
||||
"rentalCancellationConfirmationToUser",
|
||||
confirmationVariables
|
||||
);
|
||||
@@ -1099,7 +1155,7 @@ class EmailService {
|
||||
additionalInfo: additionalInfo,
|
||||
};
|
||||
|
||||
const notificationHtml = this.renderTemplate(
|
||||
const notificationHtml = await this.renderTemplate(
|
||||
"rentalCancellationNotificationToUser",
|
||||
notificationVariables
|
||||
);
|
||||
@@ -1112,7 +1168,9 @@ class EmailService {
|
||||
|
||||
if (notificationResult.success) {
|
||||
console.log(
|
||||
`Cancellation notification email sent to ${cancelledBy === "owner" ? "renter" : "owner"}: ${notificationRecipient}`
|
||||
`Cancellation notification email sent to ${
|
||||
cancelledBy === "owner" ? "renter" : "owner"
|
||||
}: ${notificationRecipient}`
|
||||
);
|
||||
results.notificationEmailSent = true;
|
||||
}
|
||||
@@ -1130,7 +1188,7 @@ class EmailService {
|
||||
}
|
||||
|
||||
async sendTemplateEmail(toEmail, subject, templateName, variables = {}) {
|
||||
const htmlContent = this.renderTemplate(templateName, variables);
|
||||
const htmlContent = await this.renderTemplate(templateName, variables);
|
||||
return await this.sendEmail(toEmail, subject, htmlContent);
|
||||
}
|
||||
|
||||
@@ -1413,7 +1471,7 @@ class EmailService {
|
||||
viewItemUrl: `${frontendUrl}/items/${item.id}`,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"firstListingCelebrationToOwner",
|
||||
variables
|
||||
);
|
||||
@@ -1428,7 +1486,12 @@ class EmailService {
|
||||
|
||||
// Fetch owner details
|
||||
const owner = await User.findByPk(rental.ownerId, {
|
||||
attributes: ["email", "firstName", "lastName", "stripeConnectedAccountId"],
|
||||
attributes: [
|
||||
"email",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"stripeConnectedAccountId",
|
||||
],
|
||||
});
|
||||
|
||||
// Fetch renter details
|
||||
@@ -1487,7 +1550,9 @@ class EmailService {
|
||||
stripeSection = `
|
||||
<div class="warning-box">
|
||||
<p><strong>⚠️ Action Required: Set Up Your Earnings Account</strong></p>
|
||||
<p>To receive your payout of <strong>\$${payoutAmount.toFixed(2)}</strong> when this rental completes, you need to set up your earnings account.</p>
|
||||
<p>To receive your payout of <strong>\$${payoutAmount.toFixed(
|
||||
2
|
||||
)}</strong> when this rental completes, you need to set up your earnings account.</p>
|
||||
</div>
|
||||
<h2>Set Up Earnings to Get Paid</h2>
|
||||
<div class="info-box">
|
||||
@@ -1511,19 +1576,23 @@ class EmailService {
|
||||
stripeSection = `
|
||||
<div class="success-box">
|
||||
<p><strong>✓ Earnings Account Active</strong></p>
|
||||
<p>Your earnings account is set up. You'll automatically receive \$${payoutAmount.toFixed(2)} when this rental completes.</p>
|
||||
<p>Your earnings account is set up. You'll automatically receive \$${payoutAmount.toFixed(
|
||||
2
|
||||
)} when this rental completes.</p>
|
||||
<p><a href="${frontendUrl}/earnings" style="color: #155724; text-decoration: underline;">View your earnings dashboard →</a></p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Format delivery method for display
|
||||
const deliveryMethodDisplay = rental.deliveryMethod === "delivery" ? "Delivery" : "Pickup";
|
||||
const deliveryMethodDisplay =
|
||||
rental.deliveryMethod === "delivery" ? "Delivery" : "Pickup";
|
||||
|
||||
const variables = {
|
||||
ownerName: owner.firstName || "there",
|
||||
itemName: rental.item?.name || "your item",
|
||||
renterName: `${renter.firstName} ${renter.lastName}`.trim() || "The renter",
|
||||
renterName:
|
||||
`${renter.firstName} ${renter.lastName}`.trim() || "The renter",
|
||||
startDate: rental.startDateTime
|
||||
? new Date(rental.startDateTime).toLocaleString("en-US", {
|
||||
dateStyle: "medium",
|
||||
@@ -1543,7 +1612,7 @@ class EmailService {
|
||||
rentalDetailsUrl: `${frontendUrl}/owning?rentalId=${rental.id}`,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"rentalApprovalConfirmationToOwner",
|
||||
variables
|
||||
);
|
||||
@@ -1563,7 +1632,12 @@ class EmailService {
|
||||
try {
|
||||
// Fetch owner details with Stripe info
|
||||
const owner = await User.findByPk(rental.ownerId, {
|
||||
attributes: ["email", "firstName", "lastName", "stripeConnectedAccountId"],
|
||||
attributes: [
|
||||
"email",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"stripeConnectedAccountId",
|
||||
],
|
||||
});
|
||||
|
||||
// Fetch renter details
|
||||
@@ -1572,9 +1646,7 @@ class EmailService {
|
||||
});
|
||||
|
||||
if (!owner || !renter) {
|
||||
console.error(
|
||||
"Owner or renter not found for rental completion emails"
|
||||
);
|
||||
console.error("Owner or renter not found for rental completion emails");
|
||||
return { success: false, error: "User not found" };
|
||||
}
|
||||
|
||||
@@ -1641,7 +1713,7 @@ class EmailService {
|
||||
browseItemsUrl: `${frontendUrl}/`,
|
||||
};
|
||||
|
||||
const renterHtmlContent = this.renderTemplate(
|
||||
const renterHtmlContent = await this.renderTemplate(
|
||||
"rentalCompletionThankYouToRenter",
|
||||
renterVariables
|
||||
);
|
||||
@@ -1709,7 +1781,9 @@ class EmailService {
|
||||
stripeSection = `
|
||||
<div class="warning-box">
|
||||
<p><strong>⚠️ Action Required: Set Up Your Earnings Account</strong></p>
|
||||
<p>To receive your payout of <strong>\$${payoutAmount.toFixed(2)}</strong>, you need to set up your earnings account.</p>
|
||||
<p>To receive your payout of <strong>\$${payoutAmount.toFixed(
|
||||
2
|
||||
)}</strong>, you need to set up your earnings account.</p>
|
||||
</div>
|
||||
<h2>Set Up Earnings to Get Paid</h2>
|
||||
<div class="info-box">
|
||||
@@ -1733,7 +1807,9 @@ class EmailService {
|
||||
stripeSection = `
|
||||
<div class="success-box">
|
||||
<p><strong>✓ Earnings Account Active</strong></p>
|
||||
<p>Your earnings account is set up. You'll automatically receive \$${payoutAmount.toFixed(2)} when the rental period ends.</p>
|
||||
<p>Your earnings account is set up. You'll automatically receive \$${payoutAmount.toFixed(
|
||||
2
|
||||
)} when the rental period ends.</p>
|
||||
<p><a href="${frontendUrl}/earnings" style="color: #155724; text-decoration: underline;">View your earnings dashboard →</a></p>
|
||||
</div>
|
||||
`;
|
||||
@@ -1744,7 +1820,8 @@ class EmailService {
|
||||
const ownerVariables = {
|
||||
ownerName: owner.firstName || "there",
|
||||
itemName: rental.item?.name || "your item",
|
||||
renterName: `${renter.firstName} ${renter.lastName}`.trim() || "The renter",
|
||||
renterName:
|
||||
`${renter.firstName} ${renter.lastName}`.trim() || "The renter",
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
returnedDate: returnedDate,
|
||||
@@ -1753,7 +1830,7 @@ class EmailService {
|
||||
owningUrl: `${frontendUrl}/owning`,
|
||||
};
|
||||
|
||||
const ownerHtmlContent = this.renderTemplate(
|
||||
const ownerHtmlContent = await this.renderTemplate(
|
||||
"rentalCompletionCongratsToOwner",
|
||||
ownerVariables
|
||||
);
|
||||
@@ -1802,7 +1879,7 @@ class EmailService {
|
||||
year: new Date().getFullYear(),
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"feedbackConfirmationToUser",
|
||||
variables
|
||||
);
|
||||
@@ -1815,7 +1892,8 @@ class EmailService {
|
||||
}
|
||||
|
||||
async sendFeedbackNotificationToAdmin(user, feedback) {
|
||||
const adminEmail = process.env.FEEDBACK_EMAIL || process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
const adminEmail =
|
||||
process.env.FEEDBACK_EMAIL || process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
|
||||
if (!adminEmail) {
|
||||
console.warn("No admin email configured for feedback notifications");
|
||||
@@ -1839,7 +1917,7 @@ class EmailService {
|
||||
year: new Date().getFullYear(),
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate(
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"feedbackNotificationToAdmin",
|
||||
variables
|
||||
);
|
||||
@@ -1870,7 +1948,10 @@ class EmailService {
|
||||
timestamp: timestamp,
|
||||
};
|
||||
|
||||
const htmlContent = this.renderTemplate("newMessageToUser", variables);
|
||||
const htmlContent = await this.renderTemplate(
|
||||
"newMessageToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `New message from ${sender.firstName} ${sender.lastName}`;
|
||||
|
||||
@@ -1888,6 +1969,207 @@ class EmailService {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async sendForumCommentNotification(postAuthor, commenter, post, comment) {
|
||||
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.renderTemplate(
|
||||
"forumCommentToPostAuthor",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `${commenter.firstName} ${commenter.lastName} commented on your post`;
|
||||
|
||||
const result = await this.sendEmail(
|
||||
postAuthor.email,
|
||||
subject,
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Forum comment notification email sent to ${postAuthor.email}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to send forum comment notification email:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async sendForumReplyNotification(
|
||||
commentAuthor,
|
||||
replier,
|
||||
post,
|
||||
reply,
|
||||
parentComment
|
||||
) {
|
||||
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.renderTemplate(
|
||||
"forumReplyToCommentAuthor",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `${replier.firstName} ${replier.lastName} replied to your comment`;
|
||||
|
||||
const result = await this.sendEmail(
|
||||
commentAuthor.email,
|
||||
subject,
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Forum reply notification email sent to ${commentAuthor.email}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to send forum reply notification email:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async sendForumAnswerAcceptedNotification(
|
||||
commentAuthor,
|
||||
postAuthor,
|
||||
post,
|
||||
comment
|
||||
) {
|
||||
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.renderTemplate(
|
||||
"forumAnswerAcceptedToCommentAuthor",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `Your comment was marked as the accepted answer!`;
|
||||
|
||||
const result = await this.sendEmail(
|
||||
commentAuthor.email,
|
||||
subject,
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Forum answer accepted notification email sent to ${commentAuthor.email}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to send forum answer accepted notification email:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async sendForumThreadActivityNotification(
|
||||
participant,
|
||||
commenter,
|
||||
post,
|
||||
comment
|
||||
) {
|
||||
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.renderTemplate(
|
||||
"forumThreadActivityToParticipant",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `New activity on a post you're following`;
|
||||
|
||||
const result = await this.sendEmail(
|
||||
participant.email,
|
||||
subject,
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Forum thread activity notification email sent to ${participant.email}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to send forum thread activity notification email:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new EmailService();
|
||||
|
||||
Reference in New Issue
Block a user