import { IUserRepository } from '../../../Domain/IRepository/IUserRepository'; import { CreateUserCommand } from './CreateUserCommand'; import { ShortUserDto } from '../../DTOs/UserDto'; import { UserAggregate, UserState } from '../../../Domain/User/UserAggregate'; import { UserMapper } from '../../DTOs/Mappers/UserMapper'; import { PasswordService } from '../../Services/PasswordService'; import { EmailService } from '../../Services/EmailService'; import { TokenService } from '../../Services/TokenService'; import { logDatabase, logError, logAuth, logWarning } from '../../Services/Logger'; export class CreateUserCommandHandler { constructor( private readonly userRepo: IUserRepository, private readonly emailService: EmailService ) {} async execute(cmd: CreateUserCommand): Promise { try { // Validate password strength const passwordValidation = PasswordService.validatePasswordStrength(cmd.password); if (!passwordValidation.isValid) { throw new Error(`Password validation failed: ${passwordValidation.errors.join(', ')}`); } const user = new UserAggregate(); user.username = cmd.username; // Hash the password before storing user.password = await PasswordService.hashPassword(cmd.password); // Generate verification token const verificationTokenData = TokenService.generateVerificationToken(); user.token = await TokenService.hashToken(verificationTokenData.token); user.TokenExpires = verificationTokenData.expiresAt; user.email = cmd.email; user.fname = cmd.fname; user.lname = cmd.lname; user.orgid = cmd.orgid || null; user.phone = cmd.phone || null; user.state = UserState.REGISTERED_NOT_VERIFIED; const created = await this.userRepo.create(user); // Send verification email (non-blocking) this.sendVerificationEmailAsync(created, verificationTokenData.token); return UserMapper.toShortDto(created); } catch (error) { // Only log the error once here, don't log again in router const errorMessage = (error as Error).message; // Re-throw validation errors as-is (don't log as these are user input errors) if (errorMessage.includes('Password validation failed')) { throw error; } // Handle database constraint errors if (errorMessage.includes('duplicate') || errorMessage.includes('unique') || errorMessage.includes('UNIQUE constraint') || errorMessage.includes('already exists')) { throw new Error('User with this username or email already exists'); } // Log database/system errors but throw user-friendly message logError('CreateUserCommandHandler error', error as Error); throw new Error('Failed to create user'); } } private async sendVerificationEmailAsync(user: UserAggregate, token: string): Promise { try { const baseUrl = process.env.FRONTEND_URL || 'http://localhost:5173'; const verificationUrl = TokenService.generateVerificationUrl(baseUrl, token); const emailSent = await this.emailService.sendVerificationEmail( user.email, `${user.fname} ${user.lname}`, token, verificationUrl ); if (!emailSent) { logWarning('Failed to send verification email', { email: user.email, userId: user.id }); } else { logAuth('Verification email sent successfully', user.id, { email: user.email }); } } catch (emailError) { logError('Error sending verification email', emailError as Error); } } }