278 lines
No EOL
8.6 KiB
JavaScript
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; |