workspace-v1/shared/catalog/notifications/_reference/notification.service.reference.ts
rckrdmrd cb4c0681d3 feat(workspace): Add new projects and update architecture
New projects created:
- michangarrito (marketplace mobile)
- template-saas (SaaS template)
- clinica-dental (dental ERP)
- clinica-veterinaria (veterinary ERP)

Architecture updates:
- Move catalog from core/ to shared/
- Add MCP servers structure and templates
- Add git management scripts
- Update SUBREPOSITORIOS.md with 15 new repos
- Update .gitignore for new projects

Repository infrastructure:
- 4 main repositories
- 11 subrepositorios
- Gitea remotes configured

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:43:28 -06:00

233 lines
6.1 KiB
TypeScript

/**
* 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<Notification>,
@InjectRepository(NotificationPreference, 'notifications')
private readonly preferenceRepo: Repository<NotificationPreference>,
// 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<Notification> {
// 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<void> {
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<number> {
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<number> {
return this.notificationRepo.count({
where: { user_id: userId, is_read: false },
});
}
/**
* Eliminar notificación
*/
async delete(notificationId: string, userId: string): Promise<void> {
await this.notificationRepo.delete({
id: notificationId,
user_id: userId,
});
}
// ============ HELPERS PRIVADOS ============
private async getUserPreferences(userId: string): Promise<UserPreferences> {
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<void> {
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<void> {
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<string, any>;
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<string, any>;
is_read: boolean;
read_at?: Date;
created_at: Date;
}