E-Commerce-Module/backend/src/services/emailService.js

278 lines
No EOL
8.6 KiB
JavaScript

const nodemailer = require('nodemailer');
const config = require('../config');
const { query, pool } = require('../db');
/**
* Service for sending emails with templates
*/
const emailService = {
/**
* Create email transporter
* @returns {Object} Configured nodemailer transporter
*/
createTransporter() {
return nodemailer.createTransport({
host: config.email.host,
port: config.email.port,
auth: {
user: config.email.user,
pass: config.email.pass
}
});
},
/**
* Get a template by type, preferring the default one
* @param {string} type - Template type
* @returns {Promise<Object|null>} Template object or null if not found
*/
async getTemplateByType(type) {
try {
// Get all settings with 'email_templates' category
const result = await query(
'SELECT * FROM system_settings WHERE category = $1',
['email_templates']
);
// Find the default template for the specified type
let defaultTemplate = null;
let fallbackTemplate = null;
for (const setting of result.rows) {
try {
console.log(setting.value, typeof setting.value)
const templateData = JSON.parse(setting.value);
if (templateData.type === type) {
if (templateData.isDefault) {
defaultTemplate = {
id: setting.key,
...templateData
};
break; // Found the default template
} else if (!fallbackTemplate) {
// Keep a fallback template in case no default is found
fallbackTemplate = {
id: setting.key,
...templateData
};
}
}
} catch (e) {
console.error(`Failed to parse template setting: ${setting.key}`, e);
}
}
// Return default template if found, otherwise return fallback or null
return defaultTemplate || fallbackTemplate || null;
} catch (error) {
console.error('Error getting template by type:', error);
return null;
}
},
/**
* Replace template variables with actual values
* @param {string} content - Template content
* @param {Object} variables - Variable values
* @returns {string} Processed content
*/
replaceVariables(content, variables) {
let processedContent = content;
if (variables) {
for (const [key, value] of Object.entries(variables)) {
const placeholder = `{{${key}}}`;
const regex = new RegExp(placeholder, 'g');
processedContent = processedContent.replace(regex, value || '');
}
}
return processedContent;
},
/**
* Send an email using a template
* @param {Object} options - Email options
* @param {string} options.to - Recipient email address
* @param {string} options.templateType - Template type
* @param {Object} options.variables - Template variables
* @param {string} [options.from] - Sender email (optional, defaults to config)
* @param {string} [options.subject] - Custom subject (optional, defaults to template subject)
* @param {string} [options.cc] - CC recipients (optional)
* @param {string} [options.bcc] - BCC recipients (optional)
* @returns {Promise<boolean>} Success status
*/
async sendTemplatedEmail(options) {
try {
const { to, templateType, variables, from, subject, cc, bcc } = options;
// Get template
const template = await this.getTemplateByType(templateType);
if (!template) {
throw new Error(`No template found for type: ${templateType}`);
}
// Replace variables in content and subject
const emailContent = this.replaceVariables(template.content, variables);
const emailSubject = subject || this.replaceVariables(template.subject, variables);
// Create transporter
const transporter = this.createTransporter();
// Send email
const result = await transporter.sendMail({
from: from || config.email.reply,
to,
cc,
bcc,
subject: emailSubject,
html: emailContent
});
console.log(`Email sent: ${result.messageId}`);
return true;
} catch (error) {
console.error('Error sending templated email:', error);
throw error;
}
},
/**
* Send a login code email
* @param {Object} options - Options
* @param {string} options.to - Recipient email
* @param {string} options.code - Login verification code
* @param {string} options.loginLink - Direct login link
* @returns {Promise<boolean>} Success status
*/
async sendLoginCodeEmail(options) {
const { to, code, loginLink } = options;
return this.sendTemplatedEmail({
to,
templateType: 'login_code',
variables: {
code,
loginLink,
email: to
}
});
},
/**
* Send a shipping notification email
* @param {Object} options - Options
* @param {string} options.to - Recipient email
* @param {string} options.first_name - Customer's first name
* @param {string} options.order_id - Order ID
* @param {string} options.tracking_number - Tracking number
* @param {string} options.carrier - Shipping carrier
* @param {string} options.tracking_link - Tracking link
* @param {string} options.shipped_date - Ship date
* @param {string} options.estimated_delivery - Estimated delivery
* @param {string} options.items_html - Order items HTML table
* @param {string} options.customer_message - Custom message
* @returns {Promise<boolean>} Success status
*/
async sendShippingNotification(options) {
return this.sendTemplatedEmail({
to: options.to,
templateType: 'shipping_notification',
variables: options
});
},
/**
* Send an order confirmation email
* @param {Object} options - Options
* @param {string} options.to - Recipient email
* @param {string} options.first_name - Customer's first name
* @param {string} options.order_id - Order ID
* @param {string} options.order_date - Order date
* @param {string} options.order_total - Order total
* @param {string} options.shipping_address - Shipping address
* @param {string} options.items_html - Order items HTML table
* @returns {Promise<boolean>} Success status
*/
async sendOrderConfirmation(options) {
return this.sendTemplatedEmail({
to: options.to,
templateType: 'order_confirmation',
variables: options
});
},
/**
* Send a low stock alert email
* @param {Object} options - Options
* @param {string} options.to - Recipient email
* @param {string} options.product_name - Product name
* @param {string} options.current_stock - Current stock level
* @param {string} options.threshold - Stock threshold
* @returns {Promise<boolean>} Success status
*/
async sendLowStockAlert(options) {
return this.sendTemplatedEmail({
to: options.to,
templateType: 'low_stock_alert',
variables: options
});
},
/**
* Send a welcome email
* @param {Object} options - Options
* @param {string} options.to - Recipient email
* @param {string} options.first_name - User's first name
* @returns {Promise<boolean>} Success status
*/
async sendWelcomeEmail(options) {
return this.sendTemplatedEmail({
to: options.to,
templateType: 'welcome_email',
variables: {
first_name: options.first_name,
email: options.to
}
});
},
/**
* Log an email in the database
* @param {Object} emailData - Email data to log
* @param {string} emailData.recipient - Recipient email
* @param {string} emailData.subject - Email subject
* @param {string} emailData.sent_by - User ID who sent the email
* @param {string} [emailData.template_id] - Template ID used
* @param {string} [emailData.template_type] - Template type used
* @returns {Promise<Object>} Log entry
*/
async logEmail(emailData) {
try {
const result = await query(
`INSERT INTO email_logs
(recipient, subject, sent_by, template_id, template_type, status)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *`,
[
emailData.recipient,
emailData.subject,
emailData.sent_by,
emailData.template_id || null,
emailData.template_type || null,
'sent'
]
);
return result.rows[0];
} catch (error) {
console.error('Error logging email:', error);
// Don't throw error, just log it
return null;
}
}
};
module.exports = emailService;