UNPKG

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.

1,247 lines (1,049 loc) 35.5 kB
const fs = require('fs'); const path = require('path'); const Handlebars = require('handlebars'); class NotificationsModuleInitializer { constructor(projectPath, config = {}) { this.projectPath = projectPath; this.config = { enablePush: config.enablePush !== false, enableEmail: config.enableEmail !== false, enableSMS: config.enableSMS || false, enableWebhook: config.enableWebhook || false, enableInApp: config.enableInApp !== false, enableQueue: config.enableQueue !== false, enableScheduler: config.enableScheduler !== false, enableAnalytics: config.enableAnalytics !== false, pushProvider: config.pushProvider || 'firebase', // firebase, apns smsProvider: config.smsProvider || 'twilio', templateEngine: config.templateEngine || 'handlebars', queueProvider: config.queueProvider || 'bull', ...config }; this.modulePath = path.join(__dirname); } async initialize() { console.log('✅ Inicializando módulo de notificaciones...'); try { // 1. Configurar estructura de directorios await this.setupDirectories(); // 2. Generar servicio principal await this.generateNotificationService(); // 3. Generar gateway para WebSocket await this.generateNotificationGateway(); // 4. Generar proveedores await this.generateProviders(); // 5. Generar sistema de colas if (this.config.enableQueue) { await this.generateQueueSystem(); } // 6. Generar programador if (this.config.enableScheduler) { await this.generateScheduler(); } // 7. Generar sistema de plantillas await this.generateTemplateSystem(); // 8. Generar interfaces y DTOs await this.generateInterfaces(); // 9. Generar entidades await this.generateEntities(); // 10. Generar guards y decoradores await this.generateGuardsAndDecorators(); // 11. Generar controlador await this.generateController(); // 12. Generar módulo await this.generateModule(); // 13. Configurar base de datos await this.setupDatabase(); // 14. Actualizar package.json await this.updatePackageJson(); console.log('✅ Módulo de notificaciones inicializado correctamente'); return { success: true, message: 'Notifications module initialized successfully', files: this.getGeneratedFiles(), config: this.config }; } catch (error) { console.error('❌ Error inicializando módulo de notificaciones:', error); throw error; } } async setupDirectories() { const dirs = [ 'src/notifications', 'src/notifications/providers', 'src/notifications/queue', 'src/notifications/scheduler', 'src/notifications/templates', 'src/notifications/interfaces', 'src/notifications/dto', 'src/notifications/entities', 'src/notifications/guards', 'src/notifications/decorators' ]; for (const dir of dirs) { const fullPath = path.join(this.projectPath, dir); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } } } async generateNotificationService() { const templatePath = path.join(this.modulePath, 'templates', 'notifications.service.ts.hbs'); const outputPath = path.join(this.projectPath, 'src/notifications/notifications.service.ts'); const template = fs.readFileSync(templatePath, 'utf8'); const compiledTemplate = Handlebars.compile(template); const content = compiledTemplate({ config: this.config, enablePush: this.config.enablePush, enableEmail: this.config.enableEmail, enableSMS: this.config.enableSMS, enableWebhook: this.config.enableWebhook, enableInApp: this.config.enableInApp, enableQueue: this.config.enableQueue, enableAnalytics: this.config.enableAnalytics }); fs.writeFileSync(outputPath, content); } async generateNotificationGateway() { const gatewayTemplate = `import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket, OnGatewayConnection, OnGatewayDisconnect, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; import { Injectable, UseGuards } from '@nestjs/common'; import { NotificationsService } from './notifications.service'; {{#if hasAuth}} import { WsAuthGuard } from '../auth/guards/ws-auth.guard'; {{/if}} @WebSocketGateway({ cors: { origin: process.env.CORS_ORIGIN || '*', }, namespace: '/notifications', }) @Injectable() export class NotificationsGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private userSockets = new Map<string, Set<string>>(); constructor( private readonly notificationsService: NotificationsService, ) {} handleConnection(client: Socket) { console.log(\`Client connected: \${client.id}\`); } handleDisconnect(client: Socket) { console.log(\`Client disconnected: \${client.id}\`); this.removeUserSocket(client); } {{#if hasAuth}} @UseGuards(WsAuthGuard) {{/if}} @SubscribeMessage('join-user-room') async handleJoinUserRoom( @MessageBody() data: { userId: string }, @ConnectedSocket() client: Socket, ) { const { userId } = data; await client.join(\`user-\${userId}\`); this.addUserSocket(userId, client.id); const pendingNotifications = await this.notificationsService.getPendingNotifications(userId); for (const notification of pendingNotifications) { client.emit('notification', notification); await this.notificationsService.markAsDelivered(notification.id); } client.emit('joined-user-room', { userId, success: true }); } async sendToUser(userId: string, notification: any) { this.server.to(\`user-\${userId}\`).emit('notification', notification); } private addUserSocket(userId: string, socketId: string) { if (!this.userSockets.has(userId)) { this.userSockets.set(userId, new Set()); } this.userSockets.get(userId)!.add(socketId); } private removeUserSocket(client: Socket) { for (const [userId, sockets] of this.userSockets.entries()) { if (sockets.has(client.id)) { sockets.delete(client.id); if (sockets.size === 0) { this.userSockets.delete(userId); } break; } } } }`; const outputPath = path.join(this.projectPath, 'src/notifications/notifications.gateway.ts'); const compiledTemplate = Handlebars.compile(gatewayTemplate); const content = compiledTemplate({ hasAuth: fs.existsSync(path.join(this.projectPath, 'src/auth')) }); fs.writeFileSync(outputPath, content); } async generateProviders() { if (this.config.enablePush && this.config.pushProvider === 'firebase') { await this.generateFirebaseProvider(); } if (this.config.enableSMS && this.config.smsProvider === 'twilio') { await this.generateTwilioProvider(); } if (this.config.enableWebhook) { await this.generateWebhookProvider(); } if (this.config.enableInApp) { await this.generateWebSocketProvider(); } } async generateFirebaseProvider() { const firebaseTemplate = `import { Injectable, Logger } from '@nestjs/common'; import * as admin from 'firebase-admin'; import { NotificationProvider } from '../interfaces/notification.interface'; @Injectable() export class FirebaseProvider implements NotificationProvider { private readonly logger = new Logger(FirebaseProvider.name); private app: admin.app.App; constructor() { this.initializeFirebase(); } private initializeFirebase() { try { const serviceAccount = { projectId: process.env.FIREBASE_PROJECT_ID, privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\\\n/g, '\\n'), clientEmail: process.env.FIREBASE_CLIENT_EMAIL, }; this.app = admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }, 'notifications'); this.logger.log('Firebase initialized successfully'); } catch (error) { this.logger.error('Failed to initialize Firebase:', error); throw error; } } async send(notification: any): Promise<any> { try { const message = { notification: { title: notification.title, body: notification.body, }, data: notification.data || {}, token: notification.deviceToken, }; const response = await this.app.messaging().send(message); this.logger.log(\`Firebase notification sent: \${response}\`); return { success: true, messageId: response, provider: 'firebase', }; } catch (error) { this.logger.error('Failed to send Firebase notification:', error); throw error; } } getProviderName(): string { return 'firebase'; } }`; const outputPath = path.join(this.projectPath, 'src/notifications/providers/firebase.provider.ts'); fs.writeFileSync(outputPath, firebaseTemplate); } async generateTwilioProvider() { const twilioTemplate = `import { Injectable, Logger } from '@nestjs/common'; import { Twilio } from 'twilio'; import { NotificationProvider } from '../interfaces/notification.interface'; @Injectable() export class TwilioProvider implements NotificationProvider { private readonly logger = new Logger(TwilioProvider.name); private client: Twilio; constructor() { this.initializeTwilio(); } private initializeTwilio() { try { this.client = new Twilio( process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN ); this.logger.log('Twilio initialized successfully'); } catch (error) { this.logger.error('Failed to initialize Twilio:', error); throw error; } } async send(notification: any): Promise<any> { try { const message = await this.client.messages.create({ body: \`\${notification.title}\\n\${notification.body}\`, from: process.env.TWILIO_PHONE_NUMBER, to: notification.phoneNumber, }); this.logger.log(\`SMS sent successfully: \${message.sid}\`); return { success: true, messageId: message.sid, provider: 'twilio', }; } catch (error) { this.logger.error('Failed to send SMS:', error); throw error; } } getProviderName(): string { return 'twilio'; } }`; const outputPath = path.join(this.projectPath, 'src/notifications/providers/twilio.provider.ts'); fs.writeFileSync(outputPath, twilioTemplate); } async generateWebhookProvider() { const webhookTemplate = `import { Injectable, Logger } from '@nestjs/common'; import axios from 'axios'; import { NotificationProvider } from '../interfaces/notification.interface'; @Injectable() export class WebhookProvider implements NotificationProvider { private readonly logger = new Logger(WebhookProvider.name); async send(notification: any): Promise<any> { try { const payload = { id: notification.id, title: notification.title, body: notification.body, data: notification.data, timestamp: new Date().toISOString(), }; const response = await axios.post(notification.webhookUrl, payload); this.logger.log(\`Webhook sent successfully to \${notification.webhookUrl}\`); return { success: true, statusCode: response.status, provider: 'webhook', }; } catch (error) { this.logger.error('Failed to send webhook:', error); throw error; } } getProviderName(): string { return 'webhook'; } }`; const outputPath = path.join(this.projectPath, 'src/notifications/providers/webhook.provider.ts'); fs.writeFileSync(outputPath, webhookTemplate); } async generateWebSocketProvider() { const websocketTemplate = `import { Injectable, Logger } from '@nestjs/common'; import { NotificationProvider } from '../interfaces/notification.interface'; import { NotificationsGateway } from '../notifications.gateway'; @Injectable() export class WebSocketProvider implements NotificationProvider { private readonly logger = new Logger(WebSocketProvider.name); constructor( private readonly notificationsGateway: NotificationsGateway, ) {} async send(notification: any): Promise<any> { try { await this.notificationsGateway.sendToUser(notification.userId, { id: notification.id, title: notification.title, body: notification.body, data: notification.data, timestamp: new Date().toISOString(), }); this.logger.log('WebSocket notification sent successfully'); return { success: true, provider: 'websocket', }; } catch (error) { this.logger.error('Failed to send WebSocket notification:', error); throw error; } } getProviderName(): string { return 'websocket'; } }`; const outputPath = path.join(this.projectPath, 'src/notifications/providers/websocket.provider.ts'); fs.writeFileSync(outputPath, websocketTemplate); } async generateQueues() { if (!this.config.enableQueue) return; const queueTemplate = `import { Process, Processor } from '@nestjs/bull'; import { Job } from 'bull'; import { Injectable, Logger } from '@nestjs/common'; import { NotificationsService } from '../notifications.service'; @Processor('notifications') @Injectable() export class NotificationProcessor { private readonly logger = new Logger(NotificationProcessor.name); constructor( private readonly notificationsService: NotificationsService, ) {} @Process('send-notification') async handleSendNotification(job: Job) { const { notificationData } = job.data; try { await this.notificationsService.processNotification(notificationData); this.logger.log(\`Notification processed successfully: \${notificationData.id}\`); } catch (error) { this.logger.error(\`Failed to process notification: \${notificationData.id}\`, error); throw error; } } @Process('send-bulk-notifications') async handleBulkNotifications(job: Job) { const { notifications } = job.data; try { for (const notification of notifications) { await this.notificationsService.processNotification(notification); } this.logger.log(\`Bulk notifications processed: \${notifications.length} items\`); } catch (error) { this.logger.error('Failed to process bulk notifications', error); throw error; } } }`; const outputPath = path.join(this.projectPath, 'src/notifications/queues/notification.processor.ts'); fs.writeFileSync(outputPath, queueTemplate); } async generateSchedulers() { if (!this.config.enableScheduler) return; const schedulerTemplate = `import { Injectable, Logger } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { NotificationsService } from '../notifications.service'; @Injectable() export class NotificationScheduler { private readonly logger = new Logger(NotificationScheduler.name); constructor( private readonly notificationsService: NotificationsService, ) {} @Cron(CronExpression.EVERY_MINUTE) async handleScheduledNotifications() { try { await this.notificationsService.processScheduledNotifications(); } catch (error) { this.logger.error('Failed to process scheduled notifications', error); } } @Cron(CronExpression.EVERY_HOUR) async cleanupOldNotifications() { try { await this.notificationsService.cleanupOldNotifications(); this.logger.log('Old notifications cleanup completed'); } catch (error) { this.logger.error('Failed to cleanup old notifications', error); } } @Cron('0 0 * * *') // Daily at midnight async generateDailyReport() { try { await this.notificationsService.generateDailyAnalytics(); this.logger.log('Daily analytics report generated'); } catch (error) { this.logger.error('Failed to generate daily analytics', error); } } }`; const outputPath = path.join(this.projectPath, 'src/notifications/schedulers/notification.scheduler.ts'); fs.writeFileSync(outputPath, schedulerTemplate); } async generateTemplates() { const templateManagerTemplate = `import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../prisma/prisma.service'; import Handlebars from 'handlebars'; @Injectable() export class NotificationTemplateService { constructor(private readonly prisma: PrismaService) {} async getTemplate(templateId: string) { return this.prisma.notificationTemplate.findUnique({ where: { id: templateId }, }); } async renderTemplate(templateId: string, data: any) { const template = await this.getTemplate(templateId); if (!template) { throw new Error(\`Template not found: \${templateId}\`); } const titleTemplate = Handlebars.compile(template.title); const bodyTemplate = Handlebars.compile(template.body); return { title: titleTemplate(data), body: bodyTemplate(data), }; } async createTemplate(data: any) { return this.prisma.notificationTemplate.create({ data, }); } async updateTemplate(id: string, data: any) { return this.prisma.notificationTemplate.update({ where: { id }, data, }); } async deleteTemplate(id: string) { return this.prisma.notificationTemplate.delete({ where: { id }, }); } }`; const outputPath = path.join(this.projectPath, 'src/notifications/templates/template.service.ts'); fs.writeFileSync(outputPath, templateManagerTemplate); } async generateInterfaces() { const interfaceTemplate = `export interface NotificationProvider { send(notification: NotificationData): Promise<NotificationResult>; getProviderName(): string; } export interface NotificationData { id: string; title: string; body: string; data?: Record<string, any>; userId?: string; deviceToken?: string; phoneNumber?: string; webhookUrl?: string; channel: NotificationChannel; priority: NotificationPriority; } export interface NotificationResult { success: boolean; messageId?: string; statusCode?: number; provider: string; error?: string; } export interface NotificationPreferences { userId: string; emailEnabled: boolean; pushEnabled: boolean; smsEnabled: boolean; inAppEnabled: boolean; webhookEnabled: boolean; } export interface BulkNotificationRequest { notifications: NotificationData[]; batchSize?: number; delayBetweenBatches?: number; } export interface NotificationAnalytics { totalSent: number; totalDelivered: number; totalFailed: number; deliveryRate: number; failureRate: number; averageDeliveryTime: number; }`; const outputPath = path.join(this.projectPath, 'src/notifications/interfaces/notification.interface.ts'); fs.writeFileSync(outputPath, interfaceTemplate); } async generateDTOs() { const dtoTemplate = `import { IsString, IsOptional, IsEnum, IsObject, IsArray, IsBoolean, IsDateString } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { NotificationChannel, NotificationPriority } from '@prisma/client'; export class CreateNotificationDto { @ApiProperty() @IsString() title: string; @ApiProperty() @IsString() body: string; @ApiProperty({ enum: NotificationChannel }) @IsEnum(NotificationChannel) channel: NotificationChannel; @ApiProperty({ enum: NotificationPriority }) @IsEnum(NotificationPriority) priority: NotificationPriority; @ApiPropertyOptional() @IsOptional() @IsString() userId?: string; @ApiPropertyOptional() @IsOptional() @IsObject() data?: Record<string, any>; @ApiPropertyOptional() @IsOptional() @IsDateString() scheduledAt?: string; @ApiPropertyOptional() @IsOptional() @IsString() templateId?: string; @ApiPropertyOptional() @IsOptional() @IsObject() templateData?: Record<string, any>; } export class BulkNotificationDto { @ApiProperty({ type: [CreateNotificationDto] }) @IsArray() notifications: CreateNotificationDto[]; @ApiPropertyOptional() @IsOptional() batchSize?: number; @ApiPropertyOptional() @IsOptional() delayBetweenBatches?: number; } export class UpdateNotificationPreferencesDto { @ApiPropertyOptional() @IsOptional() @IsBoolean() emailEnabled?: boolean; @ApiPropertyOptional() @IsOptional() @IsBoolean() pushEnabled?: boolean; @ApiPropertyOptional() @IsOptional() @IsBoolean() smsEnabled?: boolean; @ApiPropertyOptional() @IsOptional() @IsBoolean() inAppEnabled?: boolean; @ApiPropertyOptional() @IsOptional() @IsBoolean() webhookEnabled?: boolean; } export class CreateTemplateDto { @ApiProperty() @IsString() name: string; @ApiProperty() @IsString() title: string; @ApiProperty() @IsString() body: string; @ApiPropertyOptional() @IsOptional() @IsString() description?: string; @ApiProperty({ enum: NotificationChannel }) @IsEnum(NotificationChannel) channel: NotificationChannel; }`; const outputPath = path.join(this.projectPath, 'src/notifications/dto/notification.dto.ts'); fs.writeFileSync(outputPath, dtoTemplate); } async generateEntities() { // Las entidades se generan automáticamente por Prisma // Este método está aquí para mantener consistencia con la interfaz } async generateGuards() { const guardTemplate = `import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { WsException } from '@nestjs/websockets'; import { Socket } from 'socket.io'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class WsAuthGuard implements CanActivate { constructor(private readonly jwtService: JwtService) {} canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const client: Socket = context.switchToWs().getClient(); const token = this.extractTokenFromHandshake(client); if (!token) { throw new WsException('Unauthorized'); } try { const payload = this.jwtService.verify(token); client.data.user = payload; return true; } catch { throw new WsException('Unauthorized'); } } private extractTokenFromHandshake(client: Socket): string | undefined { const token = client.handshake.auth?.token || client.handshake.headers?.authorization; return token?.replace('Bearer ', ''); } }`; const outputPath = path.join(this.projectPath, 'src/notifications/guards/ws-auth.guard.ts'); fs.writeFileSync(outputPath, guardTemplate); } async generateDecorators() { const decoratorTemplate = `import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { Socket } from 'socket.io'; export const WsUser = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const client: Socket = ctx.switchToWs().getClient(); return client.data.user; }, ); export const WsUserId = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const client: Socket = ctx.switchToWs().getClient(); return client.data.user?.sub || client.data.user?.id; }, ); export const NotificationChannel = createParamDecorator( (data: string, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.headers['x-notification-channel'] || data; }, ); export const UserAgent = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.headers['user-agent']; }, ); export const ClientIP = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.ip || request.connection.remoteAddress; }, );`; const outputPath = path.join(this.projectPath, 'src/notifications/decorators/notification.decorators.ts'); fs.writeFileSync(outputPath, decoratorTemplate); } async generateController() { const controllerTemplate = `import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards, Request, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { NotificationsService } from './notifications.service'; import { CreateNotificationDto, BulkNotificationDto, UpdateNotificationPreferencesDto, CreateTemplateDto, } from './dto/notification.dto'; {{#if hasAuth}} import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; {{/if}} @ApiTags('notifications') @Controller('notifications') {{#if hasAuth}} @UseGuards(JwtAuthGuard) @ApiBearerAuth() {{/if}} export class NotificationsController { constructor(private readonly notificationsService: NotificationsService) {} @Post() @ApiOperation({ summary: 'Send a notification' }) @ApiResponse({ status: 201, description: 'Notification sent successfully' }) async sendNotification(@Body() createNotificationDto: CreateNotificationDto) { return this.notificationsService.sendNotification(createNotificationDto); } @Post('bulk') @ApiOperation({ summary: 'Send bulk notifications' }) @ApiResponse({ status: 201, description: 'Bulk notifications sent successfully' }) async sendBulkNotifications(@Body() bulkNotificationDto: BulkNotificationDto) { return this.notificationsService.sendBulkNotifications(bulkNotificationDto); } @Get('user/:userId') @ApiOperation({ summary: 'Get user notifications' }) @ApiResponse({ status: 200, description: 'User notifications retrieved successfully' }) async getUserNotifications( @Param('userId') userId: string, @Query('page') page: number = 1, @Query('limit') limit: number = 10, ) { return this.notificationsService.getUserNotifications(userId, page, limit); } @Put(':id/read') @ApiOperation({ summary: 'Mark notification as read' }) @ApiResponse({ status: 200, description: 'Notification marked as read' }) async markAsRead(@Param('id') id: string) { return this.notificationsService.markAsRead(id); } @Get('preferences/:userId') @ApiOperation({ summary: 'Get user notification preferences' }) @ApiResponse({ status: 200, description: 'User preferences retrieved successfully' }) async getUserPreferences(@Param('userId') userId: string) { return this.notificationsService.getUserPreferences(userId); } @Put('preferences/:userId') @ApiOperation({ summary: 'Update user notification preferences' }) @ApiResponse({ status: 200, description: 'Preferences updated successfully' }) async updateUserPreferences( @Param('userId') userId: string, @Body() updatePreferencesDto: UpdateNotificationPreferencesDto, ) { return this.notificationsService.updateUserPreferences(userId, updatePreferencesDto); } @Get('templates') @ApiOperation({ summary: 'Get notification templates' }) @ApiResponse({ status: 200, description: 'Templates retrieved successfully' }) async getTemplates() { return this.notificationsService.getTemplates(); } @Post('templates') @ApiOperation({ summary: 'Create notification template' }) @ApiResponse({ status: 201, description: 'Template created successfully' }) async createTemplate(@Body() createTemplateDto: CreateTemplateDto) { return this.notificationsService.createTemplate(createTemplateDto); } @Get('analytics') @ApiOperation({ summary: 'Get notification analytics' }) @ApiResponse({ status: 200, description: 'Analytics retrieved successfully' }) async getAnalytics( @Query('startDate') startDate?: string, @Query('endDate') endDate?: string, ) { return this.notificationsService.getAnalytics(startDate, endDate); } }`; const outputPath = path.join(this.projectPath, 'src/notifications/notifications.controller.ts'); const compiledTemplate = Handlebars.compile(controllerTemplate); const content = compiledTemplate({ hasAuth: fs.existsSync(path.join(this.projectPath, 'src/auth')) }); fs.writeFileSync(outputPath, content); } async generateModule() { const moduleTemplate = `import { Module } from '@nestjs/common'; import { NotificationsService } from './notifications.service'; import { NotificationsController } from './notifications.controller'; import { NotificationsGateway } from './notifications.gateway'; import { NotificationTemplateService } from './templates/template.service'; {{#if enableQueue}} import { BullModule } from '@nestjs/bull'; import { NotificationProcessor } from './queues/notification.processor'; {{/if}} {{#if enableScheduler}} import { ScheduleModule } from '@nestjs/schedule'; import { NotificationScheduler } from './schedulers/notification.scheduler'; {{/if}} {{#if enablePush}} import { FirebaseProvider } from './providers/firebase.provider'; {{/if}} {{#if enableSMS}} import { TwilioProvider } from './providers/twilio.provider'; {{/if}} {{#if enableWebhook}} import { WebhookProvider } from './providers/webhook.provider'; {{/if}} {{#if enableInApp}} import { WebSocketProvider } from './providers/websocket.provider'; {{/if}} @Module({ imports: [ {{#if enableQueue}} BullModule.registerQueue({ name: 'notifications', }), {{/if}} {{#if enableScheduler}} ScheduleModule.forRoot(), {{/if}} ], controllers: [NotificationsController], providers: [ NotificationsService, NotificationsGateway, NotificationTemplateService, {{#if enableQueue}} NotificationProcessor, {{/if}} {{#if enableScheduler}} NotificationScheduler, {{/if}} {{#if enablePush}} FirebaseProvider, {{/if}} {{#if enableSMS}} TwilioProvider, {{/if}} {{#if enableWebhook}} WebhookProvider, {{/if}} {{#if enableInApp}} WebSocketProvider, {{/if}} ], exports: [NotificationsService, NotificationsGateway], }) export class NotificationsModule {}`; Handlebars.registerHelper('eq', function(a, b) { return a === b; }); const outputPath = path.join(this.projectPath, 'src/notifications/notifications.module.ts'); const compiledTemplate = Handlebars.compile(moduleTemplate); const content = compiledTemplate(this.config); fs.writeFileSync(outputPath, content); } async setupDatabase() { const prismaSchemaPath = path.join(this.projectPath, 'prisma/schema.prisma'); if (!fs.existsSync(prismaSchemaPath)) { console.log('Prisma schema not found, skipping database setup'); return; } let schemaContent = fs.readFileSync(prismaSchemaPath, 'utf8'); const notificationModels = ` // Notification Models model Notification { id String @id @default(cuid()) title String body String channel NotificationChannel priority NotificationPriority status NotificationStatus @default(PENDING) userId String? data Json? scheduledAt DateTime? sentAt DateTime? deliveredAt DateTime? readAt DateTime? failedAt DateTime? error String? templateId String? template NotificationTemplate? @relation(fields: [templateId], references: [id]) analytics NotificationAnalytics[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("notifications") } model NotificationTemplate { id String @id @default(cuid()) name String @unique title String body String description String? channel NotificationChannel isActive Boolean @default(true) notifications Notification[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("notification_templates") } model NotificationPreferences { id String @id @default(cuid()) userId String @unique emailEnabled Boolean @default(true) pushEnabled Boolean @default(true) smsEnabled Boolean @default(false) inAppEnabled Boolean @default(true) webhookEnabled Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("notification_preferences") } model UserDevice { id String @id @default(cuid()) userId String deviceToken String @unique platform DevicePlatform isActive Boolean @default(true) lastUsedAt DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("user_devices") } model NotificationAnalytics { id String @id @default(cuid()) notificationId String notification Notification @relation(fields: [notificationId], references: [id], onDelete: Cascade) event AnalyticsEvent timestamp DateTime @default(now()) metadata Json? @@map("notification_analytics") } enum NotificationChannel { EMAIL PUSH SMS IN_APP WEBHOOK } enum NotificationPriority { LOW NORMAL HIGH URGENT } enum NotificationStatus { PENDING SENT DELIVERED READ FAILED } enum DevicePlatform { IOS ANDROID WEB } enum AnalyticsEvent { SENT DELIVERED READ CLICKED FAILED }`; if (!schemaContent.includes('model Notification')) { schemaContent += notificationModels; fs.writeFileSync(prismaSchemaPath, schemaContent); console.log('Added notification models to Prisma schema'); } } async updatePackageJson() { const packageJsonPath = path.join(this.projectPath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { console.log('package.json not found, skipping dependency updates'); return; } const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies = { '@nestjs/websockets': '^10.0.0', 'socket.io': '^4.7.0', 'handlebars': '^4.7.8', 'axios': '^1.6.0' }; if (this.config.enableQueue) { dependencies['@nestjs/bull'] = '^10.0.0'; dependencies['bull'] = '^4.12.0'; } if (this.config.enableScheduler) { dependencies['@nestjs/schedule'] = '^4.0.0'; } if (this.config.enablePush && this.config.pushProvider === 'firebase') { dependencies['firebase-admin'] = '^12.0.0'; } if (this.config.enableSMS && this.config.smsProvider === 'twilio') { dependencies['twilio'] = '^4.20.0'; } packageJson.dependencies = { ...packageJson.dependencies, ...dependencies }; fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); console.log('Updated package.json with notification dependencies'); } getGeneratedFiles() { const files = [ 'src/notifications/notifications.service.ts', 'src/notifications/notifications.controller.ts', 'src/notifications/notifications.gateway.ts', 'src/notifications/notifications.module.ts' ]; return files; } } module.exports = NotificationsModuleInitializer;