"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); 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 __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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); } }; var InvitationService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.InvitationService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("@nestjs/typeorm"); const typeorm_2 = require("typeorm"); const config_1 = require("@nestjs/config"); const crypto = __importStar(require("crypto")); const invitation_entity_1 = require("../entities/invitation.entity"); const user_entity_1 = require("../../auth/entities/user.entity"); const tenant_entity_1 = require("../../tenants/entities/tenant.entity"); const email_service_1 = require("../../email/services/email.service"); let InvitationService = InvitationService_1 = class InvitationService { constructor(invitationRepository, userRepository, tenantRepository, emailService, configService) { this.invitationRepository = invitationRepository; this.userRepository = userRepository; this.tenantRepository = tenantRepository; this.emailService = emailService; this.configService = configService; this.logger = new common_1.Logger(InvitationService_1.name); this.invitationExpirationDays = 7; } async invite(dto, inviterId, tenantId) { const email = dto.email.toLowerCase().trim(); const existingUser = await this.userRepository.findOne({ where: { email, tenant_id: tenantId }, }); if (existingUser) { throw new common_1.ConflictException('Este email ya está registrado en la organización'); } const existingInvitation = await this.invitationRepository.findOne({ where: { email, tenant_id: tenantId, status: 'pending', }, }); if (existingInvitation) { throw new common_1.ConflictException('Ya existe una invitación pendiente para este email'); } const [inviter, tenant] = await Promise.all([ this.userRepository.findOne({ where: { id: inviterId } }), this.tenantRepository.findOne({ where: { id: tenantId } }), ]); if (!inviter) { throw new common_1.NotFoundException('Usuario invitador no encontrado'); } if (!tenant) { throw new common_1.NotFoundException('Organización no encontrada'); } const token = this.generateSecureToken(); const expiresAt = new Date(); expiresAt.setDate(expiresAt.getDate() + this.invitationExpirationDays); const invitation = this.invitationRepository.create({ tenant_id: tenantId, email, token, status: 'pending', expires_at: expiresAt, created_by: inviterId, message: dto.message || null, metadata: { role: dto.role }, }); await this.invitationRepository.save(invitation); await this.sendInvitationEmail(invitation, inviter, tenant, dto.role); this.logger.log(`Invitation sent to ${email} for tenant ${tenant.name}`); return this.toResponseDto(invitation, dto.role); } async findAllByTenant(tenantId) { await this.expireOldInvitations(tenantId); const invitations = await this.invitationRepository.find({ where: { tenant_id: tenantId }, order: { created_at: 'DESC' }, }); return invitations.map((inv) => this.toResponseDto(inv, inv.metadata?.role || 'member')); } async resend(token, inviterId, tenantId) { const invitation = await this.invitationRepository.findOne({ where: { token, tenant_id: tenantId }, }); if (!invitation) { throw new common_1.NotFoundException('Invitación no encontrada'); } if (invitation.status !== 'pending') { throw new common_1.BadRequestException('Solo se pueden reenviar invitaciones pendientes'); } const [inviter, tenant] = await Promise.all([ this.userRepository.findOne({ where: { id: inviterId } }), this.tenantRepository.findOne({ where: { id: tenantId } }), ]); if (!inviter || !tenant) { throw new common_1.NotFoundException('Usuario o organización no encontrada'); } const newToken = this.generateSecureToken(); const expiresAt = new Date(); expiresAt.setDate(expiresAt.getDate() + this.invitationExpirationDays); invitation.token = newToken; invitation.expires_at = expiresAt; await this.invitationRepository.save(invitation); const role = invitation.metadata?.role || 'member'; await this.sendInvitationEmail(invitation, inviter, tenant, role); this.logger.log(`Invitation resent to ${invitation.email}`); return this.toResponseDto(invitation, role); } async cancel(id, tenantId) { const invitation = await this.invitationRepository.findOne({ where: { id, tenant_id: tenantId }, }); if (!invitation) { throw new common_1.NotFoundException('Invitación no encontrada'); } if (invitation.status !== 'pending') { throw new common_1.BadRequestException('Solo se pueden cancelar invitaciones pendientes'); } await this.invitationRepository.remove(invitation); this.logger.log(`Invitation ${id} cancelled for ${invitation.email}`); } async findByToken(token) { const invitation = await this.invitationRepository.findOne({ where: { token }, }); if (!invitation) { return null; } if (invitation.expires_at < new Date() && invitation.status === 'pending') { invitation.status = 'expired'; await this.invitationRepository.save(invitation); } return invitation; } generateSecureToken() { return crypto.randomBytes(32).toString('hex'); } async sendInvitationEmail(invitation, inviter, tenant, role) { const appUrl = this.configService.get('APP_URL', 'http://localhost:3000'); const inviteUrl = `${appUrl}/invite/${invitation.token}`; const expiresIn = `${this.invitationExpirationDays} días`; await this.emailService.sendTemplateEmail({ to: { email: invitation.email }, templateKey: 'invitation', variables: { inviterName: inviter.fullName || inviter.email, tenantName: tenant.name, role: this.translateRole(role), inviteLink: inviteUrl, expiresIn, appName: this.configService.get('APP_NAME', 'Template SaaS'), }, }); } async expireOldInvitations(tenantId) { await this.invitationRepository .createQueryBuilder() .update(invitation_entity_1.Invitation) .set({ status: 'expired' }) .where('tenant_id = :tenantId', { tenantId }) .andWhere('status = :status', { status: 'pending' }) .andWhere('expires_at < NOW()') .execute(); } translateRole(role) { const translations = { admin: 'Administrador', member: 'Miembro', viewer: 'Visor', }; return translations[role] || role; } toResponseDto(invitation, role) { return { id: invitation.id, email: invitation.email, role, status: invitation.status, expires_at: invitation.expires_at, created_at: invitation.created_at, }; } }; exports.InvitationService = InvitationService; exports.InvitationService = InvitationService = InvitationService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_1.InjectRepository)(invitation_entity_1.Invitation)), __param(1, (0, typeorm_1.InjectRepository)(user_entity_1.User)), __param(2, (0, typeorm_1.InjectRepository)(tenant_entity_1.Tenant)), __metadata("design:paramtypes", [typeorm_2.Repository, typeorm_2.Repository, typeorm_2.Repository, email_service_1.EmailService, config_1.ConfigService]) ], InvitationService); //# sourceMappingURL=invitation.service.js.map