gemtrack-backend/src/modules/identity-access/application/commands/create-user/create-user.handler.ts

110 lines
4.0 KiB
TypeScript

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<UserResponseDto> {
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);
}
}