/** * NOTIFICATION SERVICE - REFERENCE IMPLEMENTATION * * @description Servicio de notificaciones multi-canal. * Soporta notificaciones in-app, email y push. * * @usage Copiar y adaptar según necesidades del proyecto. * @origin gamilit/apps/backend/src/modules/notifications/services/notifications.service.ts */ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; // Adaptar imports según proyecto // import { Notification, NotificationPreference, UserDevice } from '../entities'; /** * Tipos de notificación */ export enum NotificationTypeEnum { INFO = 'info', SUCCESS = 'success', WARNING = 'warning', ERROR = 'error', } /** * Canales de notificación */ export enum NotificationChannelEnum { IN_APP = 'in_app', EMAIL = 'email', PUSH = 'push', } @Injectable() export class NotificationService { private readonly logger = new Logger(NotificationService.name); constructor( @InjectRepository(Notification, 'notifications') private readonly notificationRepo: Repository, @InjectRepository(NotificationPreference, 'notifications') private readonly preferenceRepo: Repository, // Inyectar servicios de email y push si están disponibles // private readonly emailService: EmailService, // private readonly pushService: PushNotificationService, ) {} /** * Enviar notificación a un usuario * * @param dto - Datos de la notificación */ async send(dto: CreateNotificationDto): Promise { // 1. Obtener preferencias del usuario const preferences = await this.getUserPreferences(dto.userId); // 2. Crear notificación en BD (siempre in-app) const notification = this.notificationRepo.create({ user_id: dto.userId, type: dto.type, title: dto.title, message: dto.message, data: dto.data, is_read: false, }); await this.notificationRepo.save(notification); // 3. Enviar por canales adicionales según preferencias if (preferences.email_enabled && dto.sendEmail !== false) { await this.sendEmail(dto); } if (preferences.push_enabled && dto.sendPush !== false) { await this.sendPush(dto); } return notification; } /** * Obtener notificaciones de un usuario */ async getByUser( userId: string, options?: { unreadOnly?: boolean; limit?: number; offset?: number; }, ): Promise<{ notifications: Notification[]; total: number }> { const query = this.notificationRepo .createQueryBuilder('n') .where('n.user_id = :userId', { userId }) .orderBy('n.created_at', 'DESC'); if (options?.unreadOnly) { query.andWhere('n.is_read = false'); } const total = await query.getCount(); if (options?.limit) { query.limit(options.limit); } if (options?.offset) { query.offset(options.offset); } const notifications = await query.getMany(); return { notifications, total }; } /** * Marcar notificación como leída */ async markAsRead(notificationId: string, userId: string): Promise { await this.notificationRepo.update( { id: notificationId, user_id: userId }, { is_read: true, read_at: new Date() }, ); } /** * Marcar todas las notificaciones como leídas */ async markAllAsRead(userId: string): Promise { const result = await this.notificationRepo.update( { user_id: userId, is_read: false }, { is_read: true, read_at: new Date() }, ); return result.affected || 0; } /** * Obtener conteo de notificaciones no leídas */ async getUnreadCount(userId: string): Promise { return this.notificationRepo.count({ where: { user_id: userId, is_read: false }, }); } /** * Eliminar notificación */ async delete(notificationId: string, userId: string): Promise { await this.notificationRepo.delete({ id: notificationId, user_id: userId, }); } // ============ HELPERS PRIVADOS ============ private async getUserPreferences(userId: string): Promise { const prefs = await this.preferenceRepo.findOne({ where: { user_id: userId }, }); // Retornar defaults si no tiene preferencias return prefs || { email_enabled: true, push_enabled: true, in_app_enabled: true, }; } private async sendEmail(dto: CreateNotificationDto): Promise { try { // Implementar envío de email // await this.emailService.send({ // to: dto.userEmail, // subject: dto.title, // template: 'notification', // context: { message: dto.message, data: dto.data }, // }); this.logger.log(`Email notification sent to user ${dto.userId}`); } catch (error) { this.logger.error(`Failed to send email notification: ${error.message}`); } } private async sendPush(dto: CreateNotificationDto): Promise { try { // Implementar envío push // await this.pushService.send(dto.userId, { // title: dto.title, // body: dto.message, // data: dto.data, // }); this.logger.log(`Push notification sent to user ${dto.userId}`); } catch (error) { this.logger.error(`Failed to send push notification: ${error.message}`); } } } // ============ TIPOS ============ interface CreateNotificationDto { userId: string; type: NotificationTypeEnum; title: string; message: string; data?: Record; sendEmail?: boolean; sendPush?: boolean; } interface UserPreferences { email_enabled: boolean; push_enabled: boolean; in_app_enabled: boolean; } interface Notification { id: string; user_id: string; type: string; title: string; message: string; data?: Record; is_read: boolean; read_at?: Date; created_at: Date; }