backend-mcp
Version:
Generador automático de backends con Node.js, Express, Prisma y módulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.
379 lines (321 loc) • 11.2 kB
JavaScript
const fs = require('fs');
const path = require('path');
const Handlebars = require('handlebars');
class EmailModuleInitializer {
constructor(config = {}) {
this.config = config;
this.modulePath = __dirname;
this.projectRoot = config.projectRoot || process.cwd();
this.templatesDir = path.join(this.modulePath, 'templates');
}
async initialize() {
try {
console.log('🔧 Inicializando módulo Email...');
// 1. Detectar proveedor de email
const provider = this.detectEmailProvider();
console.log(`📧 Proveedor detectado: ${provider}`);
// 2. Configurar estructura de directorios
this.setupDirectories();
// 3. Generar archivos de configuración
await this.generateEmailConfig(provider);
// 4. Generar servicio de email
await this.generateEmailService(provider);
// 5. Generar servicio de cola
await this.generateQueueService();
// 6. Generar templates HTML
await this.generateEmailTemplates();
// 7. Generar controlador y rutas
await this.generateControllerAndRoutes();
// 8. Configurar base de datos
await this.setupDatabase();
// 9. Actualizar package.json
await this.updatePackageJson(provider);
const result = {
success: true,
module: 'email',
provider: provider,
features: this.getEnabledFeatures(),
files: this.getGeneratedFiles(),
endpoints: this.getApiEndpoints(),
templates: this.getEmailTemplates(),
message: `Módulo Email inicializado con proveedor ${provider}`
};
console.log('✅ Módulo Email inicializado correctamente');
return result;
} catch (error) {
console.error('❌ Error inicializando módulo Email:', error.message);
throw error;
}
}
detectEmailProvider() {
const env = process.env;
if (env.RESEND_API_KEY) {
return 'resend';
} else if (env.SENDGRID_API_KEY) {
return 'sendgrid';
} else if (env.SMTP_HOST && env.SMTP_USER) {
return 'nodemailer';
} else {
// Default a nodemailer con configuración básica
return 'nodemailer';
}
}
setupDirectories() {
const dirs = [
'src/email',
'src/email/templates',
'src/email/providers',
'src/email/queue'
];
dirs.forEach(dir => {
const fullPath = path.join(this.projectRoot, dir);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
});
}
async generateEmailConfig(provider) {
const templatePath = path.join(this.templatesDir, 'email.config.ts.hbs');
const outputPath = path.join(this.projectRoot, 'src/email/email.config.ts');
const context = {
provider: provider,
config: this.config,
features: {
queue: true,
templates: true,
tracking: this.config.enableTracking || false,
attachments: this.config.enableAttachments || true
}
};
await this.renderTemplate(templatePath, outputPath, context);
}
async generateEmailService(provider) {
const templatePath = path.join(this.templatesDir, 'email.service.ts.hbs');
const outputPath = path.join(this.projectRoot, 'src/email/email.service.ts');
const context = {
provider: provider,
config: this.config,
features: {
queue: true,
templates: true,
validation: true,
logging: this.config.enableLogging !== false
}
};
await this.renderTemplate(templatePath, outputPath, context);
}
async generateQueueService() {
const templatePath = path.join(this.templatesDir, 'queue.service.ts.hbs');
const outputPath = path.join(this.projectRoot, 'src/email/queue.service.ts');
const context = {
config: this.config,
queue: {
defaultAttempts: 3,
retryDelay: 5000,
maxConcurrent: 10
}
};
await this.renderTemplate(templatePath, outputPath, context);
}
async generateEmailTemplates() {
const templates = [
{
name: 'verify-email',
subject: 'Verifica tu email',
variables: ['userName', 'verificationLink', 'companyName']
},
{
name: 'reset-password',
subject: 'Resetea tu contraseña',
variables: ['userName', 'resetLink', 'expirationTime']
},
{
name: 'welcome',
subject: 'Bienvenido',
variables: ['userName', 'loginLink', 'supportEmail']
}
];
for (const template of templates) {
const templatePath = path.join(this.templatesDir, 'html-templates', `${template.name}.html.hbs`);
const outputPath = path.join(this.projectRoot, 'src/email/templates', `${template.name}.html`);
const context = {
template: template,
config: this.config
};
await this.renderTemplate(templatePath, outputPath, context);
}
}
async generateControllerAndRoutes() {
// Generar controlador
const controllerPath = path.join(this.templatesDir, 'email.controller.ts.hbs');
const controllerOutput = path.join(this.projectRoot, 'src/email/email.controller.ts');
const controllerContext = {
config: this.config,
features: {
auth: this.config.enableAuth !== false,
validation: true,
logging: this.config.enableLogging !== false
}
};
await this.renderTemplate(controllerPath, controllerOutput, controllerContext);
// Generar rutas
const routesPath = path.join(this.templatesDir, 'email.routes.ts.hbs');
const routesOutput = path.join(this.projectRoot, 'src/email/email.routes.ts');
await this.renderTemplate(routesPath, routesOutput, controllerContext);
}
async setupDatabase() {
// Crear esquema de tablas para email
const schema = `
-- Email module tables
CREATE TABLE IF NOT EXISTS email_logs (
id SERIAL PRIMARY KEY,
to_email VARCHAR(255) NOT NULL,
from_email VARCHAR(255) NOT NULL,
subject VARCHAR(500) NOT NULL,
template_name VARCHAR(100),
status VARCHAR(50) DEFAULT 'pending',
provider VARCHAR(50),
external_id VARCHAR(255),
error_message TEXT,
sent_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS email_templates (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
subject VARCHAR(500) NOT NULL,
html_content TEXT NOT NULL,
text_content TEXT,
variables JSONB,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS email_queue (
id SERIAL PRIMARY KEY,
to_email VARCHAR(255) NOT NULL,
template_name VARCHAR(100),
variables JSONB,
priority VARCHAR(20) DEFAULT 'normal',
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 3,
status VARCHAR(50) DEFAULT 'pending',
scheduled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_email_logs_status ON email_logs(status);
CREATE INDEX IF NOT EXISTS idx_email_logs_created_at ON email_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_email_queue_status ON email_queue(status);
CREATE INDEX IF NOT EXISTS idx_email_queue_scheduled_at ON email_queue(scheduled_at);
`;
const schemaPath = path.join(this.projectRoot, 'src/email/schema.sql');
fs.writeFileSync(schemaPath, schema);
}
async updatePackageJson(provider) {
const packagePath = path.join(this.projectRoot, 'package.json');
let packageJson = {};
if (fs.existsSync(packagePath)) {
packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
}
if (!packageJson.dependencies) {
packageJson.dependencies = {};
}
// Dependencias base
const baseDependencies = {
'bull': '^4.12.0',
'ioredis': '^5.3.2',
'handlebars': '^4.7.8',
'validator': '^13.11.0'
};
// Dependencias específicas del proveedor
const providerDependencies = {
nodemailer: {
'nodemailer': '^6.9.7',
'@types/nodemailer': '^6.4.14'
},
resend: {
'resend': '^2.1.0'
},
sendgrid: {
'@sendgrid/mail': '^7.7.0'
}
};
Object.assign(packageJson.dependencies, baseDependencies);
Object.assign(packageJson.dependencies, providerDependencies[provider] || {});
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
}
async renderTemplate(templatePath, outputPath, context) {
if (!fs.existsSync(templatePath)) {
console.warn(`⚠️ Template no encontrado: ${templatePath}`);
return;
}
// Registrar helper ifEquals para Handlebars
Handlebars.registerHelper('ifEquals', function(arg1, arg2, options) {
return (arg1 == arg2) ? options.fn(this) : options.inverse(this);
});
const templateContent = fs.readFileSync(templatePath, 'utf8');
const template = Handlebars.compile(templateContent);
const result = template(context);
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
fs.writeFileSync(outputPath, result);
}
getEnabledFeatures() {
return [
'multiple-providers',
'html-templates',
'email-queue',
'template-variables',
'email-tracking',
'retry-mechanism',
'email-validation'
];
}
getGeneratedFiles() {
return [
'src/email/email.config.ts',
'src/email/email.service.ts',
'src/email/queue.service.ts',
'src/email/email.controller.ts',
'src/email/email.routes.ts',
'src/email/templates/verify-email.html',
'src/email/templates/reset-password.html',
'src/email/templates/welcome.html',
'src/email/schema.sql'
];
}
getApiEndpoints() {
return [
{ method: 'POST', path: '/email/send', description: 'Enviar email individual' },
{ method: 'POST', path: '/email/send-bulk', description: 'Enviar emails masivos' },
{ method: 'GET', path: '/email/templates', description: 'Listar templates' },
{ method: 'POST', path: '/email/test', description: 'Probar configuración' },
{ method: 'GET', path: '/email/queue/status', description: 'Estado de la cola' }
];
}
getEmailTemplates() {
return [
{
name: 'verify-email',
description: 'Verificación de email',
variables: ['userName', 'verificationLink', 'companyName']
},
{
name: 'reset-password',
description: 'Reseteo de contraseña',
variables: ['userName', 'resetLink', 'expirationTime']
},
{
name: 'welcome',
description: 'Bienvenida a nuevos usuarios',
variables: ['userName', 'loginLink', 'supportEmail']
}
];
}
}
module.exports = EmailModuleInitializer;