email refactor
This commit is contained in:
71
backend/services/email/domain/AlphaInvitationEmailService.js
Normal file
71
backend/services/email/domain/AlphaInvitationEmailService.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* AlphaInvitationEmailService handles alpha program invitation emails
|
||||
* This service is responsible for:
|
||||
* - Sending alpha access invitation codes to new testers
|
||||
*/
|
||||
class AlphaInvitationEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the alpha invitation email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("Alpha Invitation Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send alpha invitation email
|
||||
* @param {string} email - Recipient's email address
|
||||
* @param {string} code - Alpha access code
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendAlphaInvitation(email, code) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||
|
||||
const variables = {
|
||||
code: code,
|
||||
email: email,
|
||||
frontendUrl: frontendUrl,
|
||||
title: "Welcome to Alpha Testing!",
|
||||
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 = await this.templateManager.renderTemplate(
|
||||
"alphaInvitationToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
email,
|
||||
"Your Alpha Access Code - RentAll",
|
||||
htmlContent
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to send alpha invitation email:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AlphaInvitationEmailService;
|
||||
136
backend/services/email/domain/AuthEmailService.js
Normal file
136
backend/services/email/domain/AuthEmailService.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* AuthEmailService handles all authentication and account security related emails
|
||||
* This service is responsible for:
|
||||
* - Sending email verification links
|
||||
* - Sending password reset links
|
||||
* - Sending password changed confirmations
|
||||
*/
|
||||
class AuthEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the auth email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("Auth Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email verification email to new users
|
||||
* @param {Object} user - User object
|
||||
* @param {string} user.firstName - User's first name
|
||||
* @param {string} user.email - User's email address
|
||||
* @param {string} verificationToken - Email verification token
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendVerificationEmail(user, verificationToken) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||
const verificationUrl = `${frontendUrl}/verify-email?token=${verificationToken}`;
|
||||
|
||||
const variables = {
|
||||
recipientName: user.firstName || "there",
|
||||
verificationUrl: verificationUrl,
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"emailVerificationToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
user.email,
|
||||
"Verify Your Email - RentAll",
|
||||
htmlContent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send password reset email with reset link
|
||||
* @param {Object} user - User object
|
||||
* @param {string} user.firstName - User's first name
|
||||
* @param {string} user.email - User's email address
|
||||
* @param {string} resetToken - Password reset token
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendPasswordResetEmail(user, resetToken) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||
const resetUrl = `${frontendUrl}/reset-password?token=${resetToken}`;
|
||||
|
||||
const variables = {
|
||||
recipientName: user.firstName || "there",
|
||||
resetUrl: resetUrl,
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"passwordResetToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
user.email,
|
||||
"Reset Your Password - RentAll",
|
||||
htmlContent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send password changed confirmation email
|
||||
* @param {Object} user - User object
|
||||
* @param {string} user.firstName - User's first name
|
||||
* @param {string} user.email - User's email address
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendPasswordChangedEmail(user) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const timestamp = new Date().toLocaleString("en-US", {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const variables = {
|
||||
recipientName: user.firstName || "there",
|
||||
email: user.email,
|
||||
timestamp: timestamp,
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"passwordChangedToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
user.email,
|
||||
"Password Changed Successfully - RentAll",
|
||||
htmlContent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthEmailService;
|
||||
299
backend/services/email/domain/CustomerServiceEmailService.js
Normal file
299
backend/services/email/domain/CustomerServiceEmailService.js
Normal file
@@ -0,0 +1,299 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* CustomerServiceEmailService handles all customer service alert emails
|
||||
* This service is responsible for:
|
||||
* - Sending late return notifications to CS team
|
||||
* - Sending damage report notifications to CS team
|
||||
* - Sending lost item notifications to CS team
|
||||
*/
|
||||
class CustomerServiceEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the customer service email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("Customer Service Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send late return notification to customer service
|
||||
* @param {Object} rental - Rental object
|
||||
* @param {number} rental.id - Rental ID
|
||||
* @param {Date} rental.endDateTime - Scheduled end date/time
|
||||
* @param {Date} rental.actualReturnDateTime - Actual return date/time
|
||||
* @param {Object} rental.item - Item object with name property
|
||||
* @param {Object} owner - Owner user object
|
||||
* @param {string} owner.firstName - Owner's first name
|
||||
* @param {string} owner.lastName - Owner's last name
|
||||
* @param {string} owner.email - Owner's email
|
||||
* @param {Object} renter - Renter user object
|
||||
* @param {string} renter.firstName - Renter's first name
|
||||
* @param {string} renter.lastName - Renter's last name
|
||||
* @param {string} renter.email - Renter's email
|
||||
* @param {Object} lateCalculation - Late fee calculation
|
||||
* @param {number} lateCalculation.lateHours - Hours late
|
||||
* @param {number} lateCalculation.lateFee - Late fee amount
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendLateReturnToCustomerService(rental, owner, renter, lateCalculation) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const csEmail = process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
if (!csEmail) {
|
||||
console.warn("No customer service email configured");
|
||||
return { success: false, error: "No customer service email configured" };
|
||||
}
|
||||
|
||||
// Format dates
|
||||
const scheduledEnd = new Date(rental.endDateTime).toLocaleString();
|
||||
const actualReturn = new Date(rental.actualReturnDateTime).toLocaleString();
|
||||
|
||||
const variables = {
|
||||
rentalId: rental.id,
|
||||
itemName: rental.item.name,
|
||||
ownerName: `${owner.firstName} ${owner.lastName}`,
|
||||
ownerEmail: owner.email,
|
||||
renterName: `${renter.firstName} ${renter.lastName}`,
|
||||
renterEmail: renter.email,
|
||||
scheduledEnd,
|
||||
actualReturn,
|
||||
hoursLate: lateCalculation.lateHours.toFixed(1),
|
||||
lateFee: lateCalculation.lateFee.toFixed(2),
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"lateReturnToCS",
|
||||
variables
|
||||
);
|
||||
|
||||
const result = await this.emailClient.sendEmail(
|
||||
csEmail,
|
||||
"Late Return Detected - Action Required",
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Late return notification sent to customer service for rental ${rental.id}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to send late return notification to customer service:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send damage report notification to customer service
|
||||
* @param {Object} rental - Rental object
|
||||
* @param {number} rental.id - Rental ID
|
||||
* @param {Object} rental.item - Item object with name property
|
||||
* @param {Object} owner - Owner user object
|
||||
* @param {string} owner.firstName - Owner's first name
|
||||
* @param {string} owner.lastName - Owner's last name
|
||||
* @param {string} owner.email - Owner's email
|
||||
* @param {Object} renter - Renter user object
|
||||
* @param {string} renter.firstName - Renter's first name
|
||||
* @param {string} renter.lastName - Renter's last name
|
||||
* @param {string} renter.email - Renter's email
|
||||
* @param {Object} damageAssessment - Damage assessment details
|
||||
* @param {string} damageAssessment.description - Damage description
|
||||
* @param {boolean} damageAssessment.canBeFixed - Whether item can be repaired
|
||||
* @param {number} [damageAssessment.repairCost] - Repair cost if applicable
|
||||
* @param {boolean} damageAssessment.needsReplacement - Whether item needs replacement
|
||||
* @param {number} [damageAssessment.replacementCost] - Replacement cost if applicable
|
||||
* @param {Object} damageAssessment.feeCalculation - Fee calculation details
|
||||
* @param {string} damageAssessment.feeCalculation.type - Fee type (repair/replacement)
|
||||
* @param {number} damageAssessment.feeCalculation.amount - Fee amount
|
||||
* @param {Array} [damageAssessment.proofOfOwnership] - Proof of ownership documents
|
||||
* @param {Object} [lateCalculation] - Late fee calculation (optional)
|
||||
* @param {number} [lateCalculation.lateFee] - Late fee amount
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
damageAssessment,
|
||||
lateCalculation = null
|
||||
) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const csEmail = process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
if (!csEmail) {
|
||||
console.warn("No customer service email configured");
|
||||
return { success: false, error: "No customer service email configured" };
|
||||
}
|
||||
|
||||
// Calculate total fees (ensure numeric values)
|
||||
const damageFee = parseFloat(damageAssessment.feeCalculation.amount) || 0;
|
||||
const lateFee = parseFloat(lateCalculation?.lateFee || 0);
|
||||
const totalFees = damageFee + lateFee;
|
||||
|
||||
// Determine fee type description
|
||||
let feeTypeDescription = "";
|
||||
if (damageAssessment.feeCalculation.type === "repair") {
|
||||
feeTypeDescription = "Repair Cost";
|
||||
} else if (damageAssessment.feeCalculation.type === "replacement") {
|
||||
feeTypeDescription = "Replacement Cost";
|
||||
} else {
|
||||
feeTypeDescription = "Damage Assessment Fee";
|
||||
}
|
||||
|
||||
const variables = {
|
||||
rentalId: rental.id,
|
||||
itemName: rental.item.name,
|
||||
ownerName: `${owner.firstName} ${owner.lastName}`,
|
||||
ownerEmail: owner.email,
|
||||
renterName: `${renter.firstName} ${renter.lastName}`,
|
||||
renterEmail: renter.email,
|
||||
damageDescription: damageAssessment.description,
|
||||
canBeFixed: damageAssessment.canBeFixed ? "Yes" : "No",
|
||||
repairCost: damageAssessment.repairCost
|
||||
? damageAssessment.repairCost.toFixed(2)
|
||||
: "N/A",
|
||||
needsReplacement: damageAssessment.needsReplacement ? "Yes" : "No",
|
||||
replacementCost: damageAssessment.replacementCost
|
||||
? damageAssessment.replacementCost.toFixed(2)
|
||||
: "N/A",
|
||||
feeTypeDescription,
|
||||
damageFee: damageFee.toFixed(2),
|
||||
lateFee: lateFee.toFixed(2),
|
||||
totalFees: totalFees.toFixed(2),
|
||||
hasProofOfOwnership:
|
||||
damageAssessment.proofOfOwnership &&
|
||||
damageAssessment.proofOfOwnership.length > 0
|
||||
? "Yes"
|
||||
: "No",
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"damageReportToCS",
|
||||
variables
|
||||
);
|
||||
|
||||
const result = await this.emailClient.sendEmail(
|
||||
csEmail,
|
||||
"Damage Report Filed - Action Required",
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Damage report notification sent to customer service for rental ${rental.id}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to send damage report notification to customer service:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send lost item notification to customer service
|
||||
* @param {Object} rental - Rental object
|
||||
* @param {number} rental.id - Rental ID
|
||||
* @param {Date} rental.endDateTime - Scheduled return date
|
||||
* @param {Date} rental.itemLostReportedAt - When loss was reported
|
||||
* @param {Object} rental.item - Item object
|
||||
* @param {string} rental.item.name - Item name
|
||||
* @param {number} rental.item.replacementCost - Item replacement cost
|
||||
* @param {Object} owner - Owner user object
|
||||
* @param {string} owner.firstName - Owner's first name
|
||||
* @param {string} owner.lastName - Owner's last name
|
||||
* @param {string} owner.email - Owner's email
|
||||
* @param {Object} renter - Renter user object
|
||||
* @param {string} renter.firstName - Renter's first name
|
||||
* @param {string} renter.lastName - Renter's last name
|
||||
* @param {string} renter.email - Renter's email
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendLostItemToCustomerService(rental, owner, renter) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const csEmail = process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
if (!csEmail) {
|
||||
console.warn("No customer service email configured");
|
||||
return { success: false, error: "No customer service email configured" };
|
||||
}
|
||||
|
||||
// Format dates
|
||||
const reportedAt = new Date(rental.itemLostReportedAt).toLocaleString();
|
||||
const scheduledReturnDate = new Date(rental.endDateTime).toLocaleString();
|
||||
|
||||
const variables = {
|
||||
rentalId: rental.id,
|
||||
itemName: rental.item.name,
|
||||
ownerName: `${owner.firstName} ${owner.lastName}`,
|
||||
ownerEmail: owner.email,
|
||||
renterName: `${renter.firstName} ${renter.lastName}`,
|
||||
renterEmail: renter.email,
|
||||
reportedAt,
|
||||
scheduledReturnDate,
|
||||
replacementCost: parseFloat(rental.item.replacementCost).toFixed(2),
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"lostItemToCS",
|
||||
variables
|
||||
);
|
||||
|
||||
const result = await this.emailClient.sendEmail(
|
||||
csEmail,
|
||||
"Lost Item Claim Filed - Action Required",
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Lost item notification sent to customer service for rental ${rental.id}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to send lost item notification to customer service:",
|
||||
error
|
||||
);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CustomerServiceEmailService;
|
||||
131
backend/services/email/domain/FeedbackEmailService.js
Normal file
131
backend/services/email/domain/FeedbackEmailService.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* FeedbackEmailService handles all feedback-related email notifications
|
||||
* This service is responsible for:
|
||||
* - Sending feedback confirmation to users
|
||||
* - Sending feedback notifications to administrators
|
||||
*/
|
||||
class FeedbackEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the feedback email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("Feedback Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send feedback confirmation email to user
|
||||
* @param {Object} user - User object
|
||||
* @param {string} user.firstName - User's first name
|
||||
* @param {string} user.email - User's email address
|
||||
* @param {Object} feedback - Feedback object
|
||||
* @param {string} feedback.feedbackText - The feedback content
|
||||
* @param {Date} feedback.createdAt - Feedback submission timestamp
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendFeedbackConfirmation(user, feedback) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const submittedAt = new Date(feedback.createdAt).toLocaleString("en-US", {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const variables = {
|
||||
userName: user.firstName || "there",
|
||||
userEmail: user.email,
|
||||
feedbackText: feedback.feedbackText,
|
||||
submittedAt: submittedAt,
|
||||
year: new Date().getFullYear(),
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"feedbackConfirmationToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
user.email,
|
||||
"Thank You for Your Feedback - RentAll",
|
||||
htmlContent
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send feedback notification to admin
|
||||
* @param {Object} user - User object who submitted feedback
|
||||
* @param {string} user.firstName - User's first name
|
||||
* @param {string} user.lastName - User's last name
|
||||
* @param {string} user.email - User's email address
|
||||
* @param {string} user.id - User's ID
|
||||
* @param {Object} feedback - Feedback object
|
||||
* @param {string} feedback.id - Feedback ID
|
||||
* @param {string} feedback.feedbackText - The feedback content
|
||||
* @param {string} [feedback.url] - URL where feedback was submitted
|
||||
* @param {string} [feedback.userAgent] - User's browser user agent
|
||||
* @param {Date} feedback.createdAt - Feedback submission timestamp
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendFeedbackNotificationToAdmin(user, feedback) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const adminEmail =
|
||||
process.env.FEEDBACK_EMAIL || process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
|
||||
if (!adminEmail) {
|
||||
console.warn("No admin email configured for feedback notifications");
|
||||
return { success: false, error: "No admin email configured" };
|
||||
}
|
||||
|
||||
const submittedAt = new Date(feedback.createdAt).toLocaleString("en-US", {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const variables = {
|
||||
userName: `${user.firstName} ${user.lastName}`.trim() || "Unknown User",
|
||||
userEmail: user.email,
|
||||
userId: user.id,
|
||||
feedbackText: feedback.feedbackText,
|
||||
feedbackId: feedback.id,
|
||||
url: feedback.url || "Not provided",
|
||||
userAgent: feedback.userAgent || "Not provided",
|
||||
submittedAt: submittedAt,
|
||||
year: new Date().getFullYear(),
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"feedbackNotificationToAdmin",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
adminEmail,
|
||||
`New Feedback from ${user.firstName} ${user.lastName}`,
|
||||
htmlContent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FeedbackEmailService;
|
||||
318
backend/services/email/domain/ForumEmailService.js
Normal file
318
backend/services/email/domain/ForumEmailService.js
Normal file
@@ -0,0 +1,318 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
console.log("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) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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 = ForumEmailService;
|
||||
97
backend/services/email/domain/MessagingEmailService.js
Normal file
97
backend/services/email/domain/MessagingEmailService.js
Normal file
@@ -0,0 +1,97 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* MessagingEmailService handles all messaging-related email notifications
|
||||
* This service is responsible for:
|
||||
* - Sending new message notifications to users
|
||||
*/
|
||||
class MessagingEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the messaging email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("Messaging Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send new message notification email
|
||||
* @param {Object} receiver - User object of the message receiver
|
||||
* @param {string} receiver.firstName - Receiver's first name
|
||||
* @param {string} receiver.email - Receiver's email address
|
||||
* @param {Object} sender - User object of the message sender
|
||||
* @param {string} sender.id - Sender's user ID
|
||||
* @param {string} sender.firstName - Sender's first name
|
||||
* @param {string} sender.lastName - Sender's last name
|
||||
* @param {Object} message - Message object
|
||||
* @param {string} message.subject - Message subject
|
||||
* @param {string} message.content - Message content
|
||||
* @param {Date} message.createdAt - Message creation timestamp
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendNewMessageNotification(receiver, sender, message) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||
const conversationUrl = `${frontendUrl}/messages/conversations/${sender.id}`;
|
||||
|
||||
const timestamp = new Date(message.createdAt).toLocaleString("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
});
|
||||
|
||||
const variables = {
|
||||
recipientName: receiver.firstName || "there",
|
||||
senderName: `${sender.firstName} ${sender.lastName}`.trim() || "A user",
|
||||
subject: message.subject,
|
||||
messageContent: message.content,
|
||||
conversationUrl: conversationUrl,
|
||||
timestamp: timestamp,
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"newMessageToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `New message from ${sender.firstName} ${sender.lastName}`;
|
||||
|
||||
const result = await this.emailClient.sendEmail(
|
||||
receiver.email,
|
||||
subject,
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(
|
||||
`Message notification email sent to ${receiver.email} from ${sender.firstName} ${sender.lastName}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Failed to send message notification email:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessagingEmailService;
|
||||
1201
backend/services/email/domain/RentalFlowEmailService.js
Normal file
1201
backend/services/email/domain/RentalFlowEmailService.js
Normal file
File diff suppressed because it is too large
Load Diff
77
backend/services/email/domain/RentalReminderEmailService.js
Normal file
77
backend/services/email/domain/RentalReminderEmailService.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* RentalReminderEmailService handles rental reminder emails
|
||||
* This service is responsible for:
|
||||
* - Sending condition check reminders
|
||||
*/
|
||||
class RentalReminderEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the rental reminder email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("Rental Reminder Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send condition check reminder email
|
||||
* @param {string} userEmail - User's email address
|
||||
* @param {Object} notification - Notification object
|
||||
* @param {string} notification.title - Notification title
|
||||
* @param {string} notification.message - Notification message
|
||||
* @param {Object} notification.metadata - Notification metadata
|
||||
* @param {string} notification.metadata.deadline - Condition check deadline
|
||||
* @param {Object} rental - Rental object
|
||||
* @param {Object} rental.item - Item object
|
||||
* @param {string} rental.item.name - Item name
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendConditionCheckReminder(userEmail, notification, rental) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const variables = {
|
||||
title: notification.title,
|
||||
message: notification.message,
|
||||
itemName: rental?.item?.name || "Unknown Item",
|
||||
deadline: notification.metadata?.deadline
|
||||
? new Date(notification.metadata.deadline).toLocaleDateString()
|
||||
: "Not specified",
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"conditionCheckReminderToUser",
|
||||
variables
|
||||
);
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
userEmail,
|
||||
`RentAll: ${notification.title}`,
|
||||
htmlContent
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to send condition check reminder:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RentalReminderEmailService;
|
||||
77
backend/services/email/domain/UserEngagementEmailService.js
Normal file
77
backend/services/email/domain/UserEngagementEmailService.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const EmailClient = require("../core/EmailClient");
|
||||
const TemplateManager = require("../core/TemplateManager");
|
||||
|
||||
/**
|
||||
* UserEngagementEmailService handles user engagement emails
|
||||
* This service is responsible for:
|
||||
* - Sending first listing celebration emails
|
||||
* - Other user engagement and milestone emails
|
||||
*/
|
||||
class UserEngagementEmailService {
|
||||
constructor() {
|
||||
this.emailClient = new EmailClient();
|
||||
this.templateManager = new TemplateManager();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the user engagement email service
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.initialized) return;
|
||||
|
||||
await Promise.all([
|
||||
this.emailClient.initialize(),
|
||||
this.templateManager.initialize(),
|
||||
]);
|
||||
|
||||
this.initialized = true;
|
||||
console.log("User Engagement Email Service initialized successfully");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send first listing celebration email to owner
|
||||
* @param {Object} owner - Owner user object
|
||||
* @param {string} owner.firstName - Owner's first name
|
||||
* @param {string} owner.email - Owner's email address
|
||||
* @param {Object} item - Item object
|
||||
* @param {number} item.id - Item ID
|
||||
* @param {string} item.name - Item name
|
||||
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||
*/
|
||||
async sendFirstListingCelebrationEmail(owner, item) {
|
||||
if (!this.initialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||
|
||||
const variables = {
|
||||
ownerName: owner.firstName || "there",
|
||||
itemName: item.name,
|
||||
itemId: item.id,
|
||||
viewItemUrl: `${frontendUrl}/items/${item.id}`,
|
||||
};
|
||||
|
||||
const htmlContent = await this.templateManager.renderTemplate(
|
||||
"firstListingCelebrationToOwner",
|
||||
variables
|
||||
);
|
||||
|
||||
const subject = `Congratulations! Your first item is live on RentAll`;
|
||||
|
||||
return await this.emailClient.sendEmail(
|
||||
owner.email,
|
||||
subject,
|
||||
htmlContent
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to send first listing celebration email:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserEngagementEmailService;
|
||||
Reference in New Issue
Block a user