- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
317 lines
13 KiB
JavaScript
317 lines
13 KiB
JavaScript
"use strict";
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
};
|
|
var EmailService_1;
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.EmailService = void 0;
|
|
const common_1 = require("@nestjs/common");
|
|
const config_1 = require("@nestjs/config");
|
|
let EmailService = EmailService_1 = class EmailService {
|
|
constructor(configService) {
|
|
this.configService = configService;
|
|
this.logger = new common_1.Logger(EmailService_1.name);
|
|
this.isConfigured = false;
|
|
}
|
|
onModuleInit() {
|
|
const emailConfig = this.configService.get('email');
|
|
this.provider = emailConfig?.provider || 'sendgrid';
|
|
this.fromEmail = emailConfig?.from || 'noreply@example.com';
|
|
this.fromName = emailConfig?.fromName || 'Template SaaS';
|
|
this.replyTo = emailConfig?.replyTo || '';
|
|
this.sendgridApiKey = emailConfig?.sendgridApiKey || '';
|
|
this.sesRegion = emailConfig?.sesRegion || 'us-east-1';
|
|
this.sesAccessKeyId = emailConfig?.sesAccessKeyId || '';
|
|
this.sesSecretAccessKey = emailConfig?.sesSecretAccessKey || '';
|
|
this.smtpHost = emailConfig?.smtpHost || '';
|
|
this.smtpPort = emailConfig?.smtpPort || 587;
|
|
this.smtpUser = emailConfig?.smtpUser || '';
|
|
this.smtpPassword = emailConfig?.smtpPassword || '';
|
|
this.smtpSecure = emailConfig?.smtpSecure || false;
|
|
this.isConfigured = this.checkConfiguration();
|
|
if (this.isConfigured) {
|
|
this.logger.log(`Email service initialized with provider: ${this.provider}`);
|
|
}
|
|
else {
|
|
this.logger.warn('Email service not configured - emails will be logged only');
|
|
}
|
|
}
|
|
checkConfiguration() {
|
|
switch (this.provider) {
|
|
case 'sendgrid':
|
|
return !!this.sendgridApiKey;
|
|
case 'ses':
|
|
return !!(this.sesAccessKeyId && this.sesSecretAccessKey);
|
|
case 'smtp':
|
|
return !!(this.smtpHost && this.smtpUser && this.smtpPassword);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
async sendEmail(dto) {
|
|
this.logger.debug(`Sending email to ${dto.to.email}: ${dto.subject}`);
|
|
if (!this.isConfigured) {
|
|
this.logger.warn('Email not configured, logging email instead');
|
|
this.logEmail(dto);
|
|
return {
|
|
success: true,
|
|
messageId: `mock-${Date.now()}`,
|
|
provider: this.provider,
|
|
};
|
|
}
|
|
try {
|
|
switch (this.provider) {
|
|
case 'sendgrid':
|
|
return await this.sendViaSendGrid(dto);
|
|
case 'ses':
|
|
return await this.sendViaSES(dto);
|
|
case 'smtp':
|
|
return await this.sendViaSMTP(dto);
|
|
default:
|
|
throw new Error(`Unknown email provider: ${this.provider}`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
this.logger.error(`Failed to send email: ${error.message}`, error.stack);
|
|
return {
|
|
success: false,
|
|
provider: this.provider,
|
|
error: error.message,
|
|
};
|
|
}
|
|
}
|
|
async sendTemplateEmail(dto) {
|
|
const { templateKey, variables, ...emailData } = dto;
|
|
const template = await this.getTemplate(templateKey);
|
|
if (!template) {
|
|
return {
|
|
success: false,
|
|
provider: this.provider,
|
|
error: `Template not found: ${templateKey}`,
|
|
};
|
|
}
|
|
const renderedSubject = this.renderTemplate(template.subject, variables || {});
|
|
const renderedHtml = this.renderTemplate(template.html, variables || {});
|
|
const renderedText = template.text
|
|
? this.renderTemplate(template.text, variables || {})
|
|
: undefined;
|
|
return this.sendEmail({
|
|
...emailData,
|
|
subject: renderedSubject,
|
|
html: renderedHtml,
|
|
text: renderedText,
|
|
});
|
|
}
|
|
async sendBulkEmails(emails) {
|
|
const results = [];
|
|
const batchSize = 10;
|
|
for (let i = 0; i < emails.length; i += batchSize) {
|
|
const batch = emails.slice(i, i + batchSize);
|
|
const batchResults = await Promise.all(batch.map((email) => this.sendEmail(email)));
|
|
results.push(...batchResults);
|
|
}
|
|
return results;
|
|
}
|
|
async sendViaSendGrid(dto) {
|
|
const message = {
|
|
personalizations: [
|
|
{
|
|
to: [{ email: dto.to.email, name: dto.to.name }],
|
|
...(dto.cc?.length && {
|
|
cc: dto.cc.map((c) => ({ email: c.email, name: c.name })),
|
|
}),
|
|
...(dto.bcc?.length && {
|
|
bcc: dto.bcc.map((b) => ({ email: b.email, name: b.name })),
|
|
}),
|
|
},
|
|
],
|
|
from: { email: this.fromEmail, name: this.fromName },
|
|
...(this.replyTo && { reply_to: { email: this.replyTo } }),
|
|
subject: dto.subject,
|
|
content: [],
|
|
};
|
|
if (dto.text) {
|
|
message.content.push({ type: 'text/plain', value: dto.text });
|
|
}
|
|
if (dto.html) {
|
|
message.content.push({ type: 'text/html', value: dto.html });
|
|
}
|
|
if (dto.attachments?.length) {
|
|
message.attachments = dto.attachments.map((a) => ({
|
|
content: a.content,
|
|
filename: a.filename,
|
|
type: a.contentType,
|
|
disposition: 'attachment',
|
|
}));
|
|
}
|
|
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${this.sendgridApiKey}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(message),
|
|
});
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
throw new Error(`SendGrid error: ${response.status} - ${errorBody}`);
|
|
}
|
|
const messageId = response.headers.get('x-message-id') || `sg-${Date.now()}`;
|
|
return {
|
|
success: true,
|
|
messageId,
|
|
provider: 'sendgrid',
|
|
};
|
|
}
|
|
async sendViaSES(dto) {
|
|
const endpoint = `https://email.${this.sesRegion}.amazonaws.com/v2/email/outbound-emails`;
|
|
const body = {
|
|
FromEmailAddress: `${this.fromName} <${this.fromEmail}>`,
|
|
Destination: {
|
|
ToAddresses: [`${dto.to.name || ''} <${dto.to.email}>`],
|
|
...(dto.cc?.length && {
|
|
CcAddresses: dto.cc.map((c) => `${c.name || ''} <${c.email}>`),
|
|
}),
|
|
...(dto.bcc?.length && {
|
|
BccAddresses: dto.bcc.map((b) => `${b.name || ''} <${b.email}>`),
|
|
}),
|
|
},
|
|
Content: {
|
|
Simple: {
|
|
Subject: { Data: dto.subject },
|
|
Body: {
|
|
...(dto.text && { Text: { Data: dto.text } }),
|
|
...(dto.html && { Html: { Data: dto.html } }),
|
|
},
|
|
},
|
|
},
|
|
...(this.replyTo && { ReplyToAddresses: [this.replyTo] }),
|
|
};
|
|
const timestamp = new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
const date = timestamp.slice(0, 8);
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
'X-Amz-Date': timestamp,
|
|
'Host': `email.${this.sesRegion}.amazonaws.com`,
|
|
};
|
|
this.logger.warn('AWS SES: Using simplified implementation - consider using @aws-sdk/client-sesv2');
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
...headers,
|
|
'X-Amz-Security-Token': '',
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
throw new Error(`SES error: ${response.status} - ${errorBody}`);
|
|
}
|
|
const result = await response.json();
|
|
return {
|
|
success: true,
|
|
messageId: result.MessageId || `ses-${Date.now()}`,
|
|
provider: 'ses',
|
|
};
|
|
}
|
|
async sendViaSMTP(dto) {
|
|
this.logger.warn('SMTP: Requires nodemailer package - install with: npm install nodemailer');
|
|
this.logEmail(dto);
|
|
return {
|
|
success: true,
|
|
messageId: `smtp-${Date.now()}`,
|
|
provider: 'smtp',
|
|
};
|
|
}
|
|
async getTemplate(templateKey) {
|
|
const templates = {
|
|
welcome: {
|
|
subject: 'Welcome to {{appName}}!',
|
|
html: `
|
|
<h1>Welcome, {{userName}}!</h1>
|
|
<p>Thank you for joining {{appName}}. We're excited to have you on board.</p>
|
|
<p>Get started by exploring our features.</p>
|
|
<p>Best regards,<br/>The {{appName}} Team</p>
|
|
`,
|
|
text: 'Welcome, {{userName}}! Thank you for joining {{appName}}.',
|
|
},
|
|
password_reset: {
|
|
subject: 'Reset your password - {{appName}}',
|
|
html: `
|
|
<h1>Password Reset Request</h1>
|
|
<p>Hi {{userName}},</p>
|
|
<p>We received a request to reset your password. Click the link below to proceed:</p>
|
|
<p><a href="{{resetLink}}">Reset Password</a></p>
|
|
<p>This link expires in {{expiresIn}}.</p>
|
|
<p>If you didn't request this, please ignore this email.</p>
|
|
`,
|
|
text: 'Hi {{userName}}, Reset your password here: {{resetLink}}',
|
|
},
|
|
invitation: {
|
|
subject: "You've been invited to {{tenantName}}",
|
|
html: `
|
|
<h1>You're Invited!</h1>
|
|
<p>{{inviterName}} has invited you to join {{tenantName}} on {{appName}}.</p>
|
|
<p><a href="{{inviteLink}}">Accept Invitation</a></p>
|
|
<p>This invitation expires in {{expiresIn}}.</p>
|
|
`,
|
|
text: "You've been invited to {{tenantName}}. Accept here: {{inviteLink}}",
|
|
},
|
|
notification: {
|
|
subject: '{{title}}',
|
|
html: `
|
|
<h2>{{title}}</h2>
|
|
<p>{{message}}</p>
|
|
{{#if actionUrl}}
|
|
<p><a href="{{actionUrl}}">{{actionText}}</a></p>
|
|
{{/if}}
|
|
`,
|
|
text: '{{title}}: {{message}}',
|
|
},
|
|
};
|
|
return templates[templateKey] || null;
|
|
}
|
|
renderTemplate(template, variables) {
|
|
let result = template;
|
|
for (const [key, value] of Object.entries(variables)) {
|
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
result = result.replace(regex, String(value ?? ''));
|
|
}
|
|
result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, condition, content) => {
|
|
return variables[condition] ? content : '';
|
|
});
|
|
return result;
|
|
}
|
|
logEmail(dto) {
|
|
this.logger.log('=== EMAIL (NOT SENT - NO PROVIDER CONFIGURED) ===');
|
|
this.logger.log(`To: ${dto.to.email}`);
|
|
if (dto.cc?.length) {
|
|
this.logger.log(`CC: ${dto.cc.map((c) => c.email).join(', ')}`);
|
|
}
|
|
if (dto.bcc?.length) {
|
|
this.logger.log(`BCC: ${dto.bcc.map((b) => b.email).join(', ')}`);
|
|
}
|
|
this.logger.log(`Subject: ${dto.subject}`);
|
|
this.logger.log(`Body (text): ${dto.text?.substring(0, 200) || '(none)'}`);
|
|
this.logger.log(`Body (html): ${dto.html?.substring(0, 200) || '(none)'}`);
|
|
this.logger.log('================================================');
|
|
}
|
|
isEnabled() {
|
|
return this.isConfigured;
|
|
}
|
|
getProvider() {
|
|
return this.provider;
|
|
}
|
|
};
|
|
exports.EmailService = EmailService;
|
|
exports.EmailService = EmailService = EmailService_1 = __decorate([
|
|
(0, common_1.Injectable)(),
|
|
__metadata("design:paramtypes", [config_1.ConfigService])
|
|
], EmailService);
|
|
//# sourceMappingURL=email.service.js.map
|