import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { StoredUser, UserRepository } from '../../domain/repositories/UserRepository'; import type { AuthenticatedUser } from '../ports/IdentityProviderPort'; import type { IdentitySessionPort } from '../ports/IdentitySessionPort'; export type SignupWithEmailInput = { email: string; password: string; displayName: string; }; export type SignupWithEmailResult = { sessionToken: string; userId: string; displayName: string; email: string; createdAt: Date; isNewUser: boolean; }; export type SignupWithEmailErrorCode = | 'INVALID_EMAIL_FORMAT' | 'WEAK_PASSWORD' | 'INVALID_DISPLAY_NAME' | 'EMAIL_ALREADY_EXISTS' | 'REPOSITORY_ERROR'; export type SignupWithEmailApplicationError = ApplicationErrorCode< SignupWithEmailErrorCode, { message: string } >; export class SignupWithEmailUseCase { constructor( private readonly userRepository: UserRepository, private readonly sessionPort: IdentitySessionPort, private readonly logger: Logger, ) {} async execute(input: SignupWithEmailInput): Promise< Result > { // Validate email format const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(input.email)) { return Result.err({ code: 'INVALID_EMAIL_FORMAT', details: { message: 'Invalid email format' }, } as SignupWithEmailApplicationError); } // Validate password strength if (input.password.length < 8) { return Result.err({ code: 'WEAK_PASSWORD', details: { message: 'Password must be at least 8 characters' }, } as SignupWithEmailApplicationError); } // Validate display name if (!input.displayName || input.displayName.trim().length < 2) { return Result.err({ code: 'INVALID_DISPLAY_NAME', details: { message: 'Display name must be at least 2 characters' }, } as SignupWithEmailApplicationError); } // Check if email already exists const existingUser = await this.userRepository.findByEmail(input.email); if (existingUser) { return Result.err({ code: 'EMAIL_ALREADY_EXISTS', details: { message: 'An account with this email already exists' }, } as SignupWithEmailApplicationError); } try { // Hash password using PasswordHash value object const { PasswordHash } = await import('@core/identity/domain/value-objects/PasswordHash'); const passwordHash = await PasswordHash.create(input.password); // Create user const userId = this.generateUserId(); const createdAt = new Date(); const newUser: StoredUser = { id: userId, email: input.email.toLowerCase().trim(), displayName: input.displayName.trim(), passwordHash: passwordHash.value, createdAt, }; await this.userRepository.create(newUser); // Create session const authenticatedUser: AuthenticatedUser = { id: newUser.id, displayName: newUser.displayName, email: newUser.email, }; const session = await this.sessionPort.createSession(authenticatedUser); const result: SignupWithEmailResult = { sessionToken: session.token, userId: session.user.id, displayName: session.user.displayName, email: session.user.email ?? newUser.email, createdAt, isNewUser: true, }; return Result.ok(result); } catch (error) { const message = error instanceof Error && error.message ? error.message : 'Failed to execute SignupWithEmailUseCase'; this.logger.error( 'SignupWithEmailUseCase.execute failed', error instanceof Error ? error : undefined, { input }, ); return Result.err({ code: 'REPOSITORY_ERROR', details: { message }, } as SignupWithEmailApplicationError); } } private generateUserId(): string { if (typeof crypto !== 'undefined' && crypto.randomUUID) { return crypto.randomUUID(); } return 'user-' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9); } }