110 lines
4.0 KiB
TypeScript
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);
|
|
}
|
|
}
|