feat(notifications): add multi-channel notification module with Email, FCM, templates, and event listeners
- Domain: NotificationLog/NotificationPreference entities, repositories, channel value object - Infrastructure: EmailService (nodemailer/SMTP), FcmService (firebase-admin), TemplateService (Handlebars) - Application: SendNotification CQRS command, UserRegistered + AgentVerified event listeners - Presentation: NotificationsController with history, preferences, and templates endpoints - Prisma: NotificationLog and NotificationPreference models with proper indexes - Templates: Vietnamese notification templates for user.registered, agent.verified, listing.approved, inquiry.received, password.reset Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { Injectable, type OnModuleInit } from '@nestjs/common';
|
||||
import { LoggerService } from '@modules/shared/infrastructure/logger.service';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
|
||||
export interface SendEmailDto {
|
||||
to: string;
|
||||
subject: string;
|
||||
html: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class EmailService implements OnModuleInit {
|
||||
private transporter!: nodemailer.Transporter;
|
||||
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
const host = process.env['SMTP_HOST'] ?? 'localhost';
|
||||
const port = Number(process.env['SMTP_PORT'] ?? '1025');
|
||||
const user = process.env['SMTP_USER'];
|
||||
const pass = process.env['SMTP_PASS'];
|
||||
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
...(user && pass ? { auth: { user, pass } } : {}),
|
||||
});
|
||||
|
||||
this.logger.log(`Email transport configured: ${host}:${port}`, 'EmailService');
|
||||
}
|
||||
|
||||
async send(dto: SendEmailDto): Promise<{ messageId: string }> {
|
||||
const from = process.env['SMTP_FROM'] ?? 'noreply@goodgo.vn';
|
||||
|
||||
try {
|
||||
const info = await this.transporter.sendMail({
|
||||
from,
|
||||
to: dto.to,
|
||||
subject: dto.subject,
|
||||
html: dto.html,
|
||||
});
|
||||
|
||||
this.logger.log(`Email sent to ${dto.to}: ${info.messageId}`, 'EmailService');
|
||||
return { messageId: info.messageId };
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to send email to ${dto.to}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
'EmailService',
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async verify(): Promise<boolean> {
|
||||
try {
|
||||
await this.transporter.verify();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user