import { ClockPort } from "../../../../../shared/application/ports/clock.port"; import { IdGeneratorPort } from "../../../../../shared/application/ports/id-generator.port"; import { PasswordHasherPort } from "../../../../../shared/application/ports/password-hasher.port"; import { ApplicationError } from "../../../../../shared/domain/errors/application-error"; import { TenantRepository } from "../../../../tenancy/domain/repositories/tenant.repository"; import { UserAggregate } from "../../../domain/aggregates/user.aggregate"; import { RoleRepository } from "../../../domain/repositories/role.repository"; import { UserRepository } from "../../../domain/repositories/user.repository"; import { toUserResponse, UserResponseDto } from "../../mappers/user-response.mapper"; import { CreateUserCommand } from "./create-user.command"; import { TenantSubscriptionRepository } from "../../../../billing/domain/repositories/tenant-subscription.repository"; import { SubscriptionPlanRepository } from "../../../../billing/domain/repositories/subscription-plan.repository"; export class CreateUserHandler { constructor( private readonly userRepository: UserRepository, private readonly roleRepository: RoleRepository, private readonly tenantRepository: TenantRepository, private readonly subscriptionRepository: TenantSubscriptionRepository, private readonly planRepository: SubscriptionPlanRepository, private readonly passwordHasher: PasswordHasherPort, private readonly clock: ClockPort, private readonly idGenerator: IdGeneratorPort ) {} public async execute(command: CreateUserCommand): Promise { if (command.password.length < 12) { throw new ApplicationError( "Password must be at least 12 characters long", 422, "WEAK_PASSWORD" ); } const tenant = await this.tenantRepository.findById(command.tenantId); if (!tenant) { throw new ApplicationError("Tenant not found", 404, "TENANT_NOT_FOUND"); } if (tenant.status !== "active") { throw new ApplicationError("Tenant is inactive", 403, "TENANT_INACTIVE"); } // Subscription Limit Check const subscription = await this.subscriptionRepository.findByTenantId(command.tenantId); if (!subscription) { throw new ApplicationError("Subscription not found", 500, "SUBSCRIPTION_REQUIRED"); } const plan = await this.planRepository.findById(subscription.planId); if (!plan) { throw new ApplicationError("Plan not found", 500, "BILLING_CONFIGURATION_ERROR"); } if (plan.maxUsers !== -1) { const currentUserCount = await this.userRepository.countByTenantId(command.tenantId); if (currentUserCount >= plan.maxUsers) { throw new ApplicationError( `User limit reached for your ${plan.name} plan (${plan.maxUsers} users). Please upgrade to add more users.`, 403, "USER_LIMIT_REACHED" ); } } const existingUser = await this.userRepository.findByEmail( command.tenantId, command.email ); if (existingUser) { throw new ApplicationError( "A user with this email already exists in the tenant", 409, "USER_EMAIL_CONFLICT" ); } const roleCodes = command.roleCodes && command.roleCodes.length > 0 ? command.roleCodes : ["tenant_user"]; const roles = await this.roleRepository.findByCodes(command.tenantId, roleCodes); if (roles.length !== roleCodes.length) { throw new ApplicationError( "One or more roles do not exist", 422, "ROLE_NOT_FOUND" ); } const user = UserAggregate.register({ id: this.idGenerator.generate(), tenantId: command.tenantId, email: command.email, firstName: command.firstName, lastName: command.lastName, passwordHash: await this.passwordHasher.hash(command.password), roles, now: this.clock.now() }); await this.userRepository.save(user); return toUserResponse(user); } }