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,74 @@
|
||||
import { Injectable, type OnModuleInit } from '@nestjs/common';
|
||||
import { LoggerService } from '@modules/shared/infrastructure/logger.service';
|
||||
import * as admin from 'firebase-admin';
|
||||
|
||||
export interface SendPushDto {
|
||||
token: string;
|
||||
title: string;
|
||||
body: string;
|
||||
data?: Record<string, string>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FcmService implements OnModuleInit {
|
||||
private initialized = false;
|
||||
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
const serviceAccountJson = process.env['FIREBASE_SERVICE_ACCOUNT'];
|
||||
if (!serviceAccountJson) {
|
||||
this.logger.warn(
|
||||
'FIREBASE_SERVICE_ACCOUNT not set — push notifications disabled',
|
||||
'FcmService',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const serviceAccount = JSON.parse(serviceAccountJson) as admin.ServiceAccount;
|
||||
if (!admin.apps.length) {
|
||||
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
|
||||
}
|
||||
this.initialized = true;
|
||||
this.logger.log('Firebase Admin initialized for push notifications', 'FcmService');
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to initialize Firebase: ${error instanceof Error ? error.message : String(error)}`,
|
||||
'FcmService',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get isAvailable(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
async send(dto: SendPushDto): Promise<string> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('FCM not initialized — FIREBASE_SERVICE_ACCOUNT not configured');
|
||||
}
|
||||
|
||||
try {
|
||||
const messageId = await admin.messaging().send({
|
||||
token: dto.token,
|
||||
notification: {
|
||||
title: dto.title,
|
||||
body: dto.body,
|
||||
},
|
||||
data: dto.data,
|
||||
android: { priority: 'high' },
|
||||
apns: { payload: { aps: { sound: 'default' } } },
|
||||
});
|
||||
|
||||
this.logger.log(`Push sent to token ${dto.token.slice(0, 10)}...: ${messageId}`, 'FcmService');
|
||||
return messageId;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to send push: ${error instanceof Error ? error.message : String(error)}`,
|
||||
'FcmService',
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user