const fs = require('fs').promises; const path = require('path'); class EmailTemplateService { constructor() { this.templatesDir = path.join(__dirname, '../templates/email'); this.cache = new Map(); } async loadTemplate(templateName, type = 'html') { const cacheKey = `${templateName}.${type}`; // Check cache first if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } try { const templatePath = path.join(this.templatesDir, `${templateName}.${type}`); const template = await fs.readFile(templatePath, 'utf-8'); // Cache the template this.cache.set(cacheKey, template); return template; } catch (error) { console.error(`Failed to load email template ${templateName}.${type}:`, error); throw new Error(`Email template not found: ${templateName}.${type}`); } } processTemplate(template, variables) { if (!template) return ''; let processed = template; // Handle conditional blocks {{#if VARIABLE}}...{{/if}} processed = processed.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, varName, content) => { const value = variables[varName]; // Check if value exists and is not empty string if (value !== undefined && value !== null && value !== '') { // Recursively process the content inside the conditional block return this.processTemplate(content, variables); } return ''; }); // Replace variables {{VARIABLE}} processed = processed.replace(/\{\{(\w+)\}\}/g, (match, varName) => { const value = variables[varName]; // Return the value or empty string if undefined return value !== undefined && value !== null ? String(value) : ''; }); // Handle line breaks in MESSAGE field for HTML templates if (variables.MESSAGE && processed.includes('{{MESSAGE}}')) { processed = processed.replace(/\{\{MESSAGE\}\}/g, variables.MESSAGE.replace(/\n/g, '
')); } return processed; } async render(templateName, variables) { try { // Load both HTML and text versions const [htmlTemplate, textTemplate] = await Promise.all([ this.loadTemplate(templateName, 'html'), this.loadTemplate(templateName, 'txt') ]); // Add default variables const defaultVariables = { APP_NAME: process.env.APP_NAME || 'BNKops Influence Tool', TIMESTAMP: new Date().toLocaleString(), ...variables }; // Use processTemplate which handles conditionals properly return { html: this.processTemplate(htmlTemplate, defaultVariables), text: this.processTemplate(textTemplate, defaultVariables) }; } catch (error) { console.error('Failed to render email template:', error); throw error; } } // Get available template names async getAvailableTemplates() { try { const files = await fs.readdir(this.templatesDir); const templates = new Set(); files.forEach(file => { const ext = path.extname(file); const name = path.basename(file, ext); if (ext === '.html' || ext === '.txt') { templates.add(name); } }); return Array.from(templates); } catch (error) { console.error('Failed to get available templates:', error); return []; } } // Clear template cache (useful for development) clearCache() { this.cache.clear(); console.log('Email template cache cleared'); } // Check if template exists async templateExists(templateName) { try { const htmlPath = path.join(this.templatesDir, `${templateName}.html`); const txtPath = path.join(this.templatesDir, `${templateName}.txt`); // Check if at least one format exists const [htmlExists, txtExists] = await Promise.all([ fs.access(htmlPath).then(() => true).catch(() => false), fs.access(txtPath).then(() => true).catch(() => false) ]); return htmlExists || txtExists; } catch (error) { return false; } } } module.exports = new EmailTemplateService();