template-saas/apps/backend/dist/modules/notifications/services/notifications.service.js
rckrdmrd 26f0e52ca7 feat: Initial commit - template-saas
Template base para proyectos SaaS multi-tenant.

Estructura inicial:
- apps/backend (NestJS API)
- apps/frontend (React/Vite)
- apps/database (PostgreSQL DDL)
- docs/ (Documentación)

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

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

214 lines
8.6 KiB
JavaScript

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NotificationsService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const entities_1 = require("../entities");
let NotificationsService = class NotificationsService {
constructor(notificationRepository, templateRepository, preferenceRepository) {
this.notificationRepository = notificationRepository;
this.templateRepository = templateRepository;
this.preferenceRepository = preferenceRepository;
}
async create(dto, tenantId) {
const notification = this.notificationRepository.create({
user_id: dto.userId,
tenant_id: tenantId,
type: dto.type || 'info',
channel: dto.channel || 'in_app',
title: dto.title,
message: dto.message,
data: dto.data || null,
action_url: dto.actionUrl || null,
delivery_status: 'pending',
});
const saved = await this.notificationRepository.save(notification);
if (saved.channel === 'in_app') {
saved.delivery_status = 'delivered';
saved.sent_at = new Date();
await this.notificationRepository.save(saved);
}
return saved;
}
async sendFromTemplate(dto, tenantId) {
const template = await this.templateRepository.findOne({
where: { code: dto.templateCode, is_active: true },
});
if (!template) {
throw new common_1.NotFoundException(`Template '${dto.templateCode}' no encontrado`);
}
let message = template.body;
let subject = template.subject || '';
if (dto.variables) {
for (const [key, value] of Object.entries(dto.variables)) {
const regex = new RegExp(`{{${key}}}`, 'g');
message = message.replace(regex, String(value));
subject = subject.replace(regex, String(value));
}
}
return this.create({
userId: dto.userId,
channel: template.channel,
title: subject || template.name,
message,
data: { templateCode: dto.templateCode, variables: dto.variables },
}, tenantId);
}
async findAllForUser(userId, tenantId, options = {}) {
const { page = 1, limit = 20, unreadOnly = false } = options;
const whereCondition = {
user_id: userId,
tenant_id: tenantId,
channel: 'in_app',
};
if (unreadOnly) {
whereCondition.is_read = false;
}
const [data, total] = await this.notificationRepository.findAndCount({
where: whereCondition,
order: { created_at: 'DESC' },
skip: (page - 1) * limit,
take: limit,
});
const unread = await this.notificationRepository.count({
where: {
user_id: userId,
tenant_id: tenantId,
channel: 'in_app',
is_read: false,
},
});
return { data, total, unread };
}
async markAsRead(notificationId, userId, tenantId) {
const notification = await this.notificationRepository.findOne({
where: { id: notificationId, user_id: userId, tenant_id: tenantId },
});
if (!notification) {
throw new common_1.NotFoundException('Notificación no encontrada');
}
notification.is_read = true;
notification.read_at = new Date();
return this.notificationRepository.save(notification);
}
async markAllAsRead(userId, tenantId) {
const result = await this.notificationRepository.update({
user_id: userId,
tenant_id: tenantId,
is_read: false,
}, {
is_read: true,
read_at: new Date(),
});
return result.affected || 0;
}
async delete(notificationId, userId, tenantId) {
const result = await this.notificationRepository.delete({
id: notificationId,
user_id: userId,
tenant_id: tenantId,
});
if (result.affected === 0) {
throw new common_1.NotFoundException('Notificación no encontrada');
}
}
async getUnreadCount(userId, tenantId) {
return this.notificationRepository.count({
where: {
user_id: userId,
tenant_id: tenantId,
channel: 'in_app',
is_read: false,
},
});
}
async findAllTemplates() {
return this.templateRepository.find({
where: { is_active: true },
order: { category: 'ASC', code: 'ASC' },
});
}
async findTemplateByCode(code) {
const template = await this.templateRepository.findOne({
where: { code, is_active: true },
});
if (!template) {
throw new common_1.NotFoundException(`Template '${code}' no encontrado`);
}
return template;
}
async getPreferences(userId, tenantId) {
let preferences = await this.preferenceRepository.findOne({
where: { user_id: userId, tenant_id: tenantId },
});
if (!preferences) {
preferences = this.preferenceRepository.create({
user_id: userId,
tenant_id: tenantId,
email_enabled: true,
push_enabled: true,
in_app_enabled: true,
sms_enabled: false,
marketing_emails: true,
product_updates: true,
security_alerts: true,
});
await this.preferenceRepository.save(preferences);
}
return preferences;
}
async updatePreferences(userId, tenantId, dto) {
let preferences = await this.getPreferences(userId, tenantId);
if (dto.emailEnabled !== undefined)
preferences.email_enabled = dto.emailEnabled;
if (dto.pushEnabled !== undefined)
preferences.push_enabled = dto.pushEnabled;
if (dto.inAppEnabled !== undefined)
preferences.in_app_enabled = dto.inAppEnabled;
if (dto.smsEnabled !== undefined)
preferences.sms_enabled = dto.smsEnabled;
if (dto.marketingEmails !== undefined)
preferences.marketing_emails = dto.marketingEmails;
if (dto.productUpdates !== undefined)
preferences.product_updates = dto.productUpdates;
if (dto.securityAlerts !== undefined)
preferences.security_alerts = dto.securityAlerts;
if (dto.categoryPreferences !== undefined) {
preferences.category_preferences = dto.categoryPreferences;
}
return this.preferenceRepository.save(preferences);
}
async cleanupOldNotifications(daysToKeep = 30) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const result = await this.notificationRepository.delete({
created_at: (0, typeorm_2.LessThan)(cutoffDate),
is_read: true,
});
return result.affected || 0;
}
};
exports.NotificationsService = NotificationsService;
exports.NotificationsService = NotificationsService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, typeorm_1.InjectRepository)(entities_1.Notification)),
__param(1, (0, typeorm_1.InjectRepository)(entities_1.NotificationTemplate)),
__param(2, (0, typeorm_1.InjectRepository)(entities_1.UserNotificationPreference)),
__metadata("design:paramtypes", [typeorm_2.Repository,
typeorm_2.Repository,
typeorm_2.Repository])
], NotificationsService);
//# sourceMappingURL=notifications.service.js.map