- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
238 lines
10 KiB
JavaScript
238 lines
10 KiB
JavaScript
"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
|