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} 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} 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} 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} 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} 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} 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} 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} 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;