"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.usersService = void 0; const bcryptjs_1 = __importDefault(require("bcryptjs")); const database_1 = require("../../config/database"); const error_handler_1 = require("../../middleware/error-handler"); const roles_1 = require("../../utils/roles"); const provisioning_service_1 = require("../../services/user-provisioning/provisioning.service"); const logger_1 = require("../../utils/logger"); const userSelect = { id: true, email: true, name: true, phone: true, pronouns: true, role: true, roles: true, status: true, permissions: true, createdVia: true, expiresAt: true, expireDays: true, lastLoginAt: true, emailVerified: true, createdAt: true, updatedAt: true, }; exports.usersService = { async findAll(filters) { const { page, limit, search, role, status } = filters; const skip = (page - 1) * limit; const where = {}; if (search) { where.OR = [ { email: { contains: search, mode: 'insensitive' } }, { name: { contains: search, mode: 'insensitive' } }, ]; } if (role) where.role = role; if (status) where.status = status; const [users, total] = await Promise.all([ database_1.prisma.user.findMany({ where, select: userSelect, skip, take: limit, orderBy: { createdAt: 'desc' }, }), database_1.prisma.user.count({ where }), ]); return { users, pagination: { page, limit, total, totalPages: Math.ceil(total / limit), }, }; }, async findById(id) { const user = await database_1.prisma.user.findUnique({ where: { id }, select: userSelect, }); if (!user) { throw new error_handler_1.AppError(404, 'User not found', 'USER_NOT_FOUND'); } return user; }, async create(data) { const existing = await database_1.prisma.user.findUnique({ where: { email: data.email } }); if (existing) { throw new error_handler_1.AppError(409, 'Email already registered', 'EMAIL_EXISTS'); } const hashedPassword = await bcryptjs_1.default.hash(data.password, 12); // Compute roles array and primary role const roles = data.roles && data.roles.length > 0 ? data.roles : [data.role || 'USER']; const primaryRole = (0, roles_1.getPrimaryRole)(roles); const user = await database_1.prisma.user.create({ data: { ...data, password: hashedPassword, role: primaryRole, roles: JSON.parse(JSON.stringify(roles)), emailVerified: true, // Admin-created users are pre-verified expiresAt: data.expiresAt ? new Date(data.expiresAt) : undefined, }, select: userSelect, }); // Fire-and-forget: auto-link to existing Contact with matching email database_1.prisma.contact.findFirst({ where: { email: { equals: data.email, mode: 'insensitive' }, userId: null, mergedIntoId: null }, }).then(async (existingContact) => { if (existingContact) { await database_1.prisma.contact.update({ where: { id: existingContact.id }, data: { userId: user.id } }); logger_1.logger.info(`Auto-linked contact ${existingContact.id} to new user ${user.id}`); } }).catch(err => { logger_1.logger.warn('Auto-link contact on user creation failed:', err); }); // Fire-and-forget: provision to eager services provisioning_service_1.userProvisioningService.onUserCreated(toCMUser(user)).catch(err => { logger_1.logger.warn('User provisioning hook (create) failed:', err); }); return user; }, async update(id, data) { const existing = await database_1.prisma.user.findUnique({ where: { id } }); if (!existing) { throw new error_handler_1.AppError(404, 'User not found', 'USER_NOT_FOUND'); } if (data.email && data.email !== existing.email) { const emailTaken = await database_1.prisma.user.findUnique({ where: { email: data.email } }); if (emailTaken) { throw new error_handler_1.AppError(409, 'Email already in use', 'EMAIL_EXISTS'); } } const updateData = { ...data }; if (data.password) { updateData.password = await bcryptjs_1.default.hash(data.password, 12); } if (data.expiresAt !== undefined) { updateData.expiresAt = data.expiresAt ? new Date(data.expiresAt) : null; } // Sync roles and primary role if (data.roles) { updateData.roles = JSON.parse(JSON.stringify(data.roles)); updateData.role = (0, roles_1.getPrimaryRole)(data.roles); } else if (data.role) { // If only primary role changed, update roles array too const currentRoles = Array.isArray(existing.roles) ? existing.roles : [existing.role]; if (!currentRoles.includes(data.role)) { updateData.roles = JSON.parse(JSON.stringify([...currentRoles, data.role])); } } const user = await database_1.prisma.user.update({ where: { id }, data: updateData, select: userSelect, }); // Invalidate sessions when user is deactivated or banned const deactivatedStatuses = ['INACTIVE', 'BANNED', 'PENDING_APPROVAL', 'PENDING_VERIFICATION']; if (data.status && deactivatedStatuses.includes(data.status)) { await database_1.prisma.refreshToken.deleteMany({ where: { userId: id } }); } // Invalidate sessions when password is changed by admin if (data.password) { await database_1.prisma.refreshToken.deleteMany({ where: { userId: id } }); } // Fire-and-forget: sync changes to provisioned services provisioning_service_1.userProvisioningService.onUserUpdated(toCMUser(user), data).catch(err => { logger_1.logger.warn('User provisioning hook (update) failed:', err); }); return user; }, async delete(id) { const existing = await database_1.prisma.user.findUnique({ where: { id } }); if (!existing) { throw new error_handler_1.AppError(404, 'User not found', 'USER_NOT_FOUND'); } // Fire-and-forget: deactivate in provisioned services BEFORE deleting const cmUser = toCMUser(existing); provisioning_service_1.userProvisioningService.onUserDeactivated(cmUser).catch(err => { logger_1.logger.warn('User provisioning hook (delete) failed:', err); }); await database_1.prisma.user.delete({ where: { id } }); }, }; /** Convert a Prisma User (or select result) to the CMUser shape needed by provisioners */ function toCMUser(user) { return { id: user.id, email: user.email, name: user.name, role: user.role, roles: user.roles, status: user.status, permissions: user.permissions, }; } //# sourceMappingURL=users.service.js.map