/** * Use Case: RequestAvatarGenerationUseCase * * Handles the business logic for requesting avatar generation from a face photo. */ import { v4 as uuidv4 } from 'uuid'; import type { IAvatarGenerationRepository } from '../../domain/repositories/IAvatarGenerationRepository'; import type { FaceValidationPort } from '../ports/FaceValidationPort'; import type { AvatarGenerationPort } from '../ports/AvatarGenerationPort'; import type { Logger } from '@core/shared/application'; import { AvatarGenerationRequest } from '../../domain/entities/AvatarGenerationRequest'; import type { IRequestAvatarGenerationPresenter } from '../presenters/IRequestAvatarGenerationPresenter'; import type { RacingSuitColor } from '../../domain/types/AvatarGenerationRequest'; export interface RequestAvatarGenerationInput { userId: string; facePhotoData: string; suitColor: RacingSuitColor; style?: 'realistic' | 'cartoon' | 'pixel-art'; } export class RequestAvatarGenerationUseCase { constructor( private readonly avatarRepo: IAvatarGenerationRepository, private readonly faceValidation: FaceValidationPort, private readonly avatarGeneration: AvatarGenerationPort, private readonly logger: Logger, ) {} async execute( input: RequestAvatarGenerationInput, presenter: IRequestAvatarGenerationPresenter, ): Promise { try { this.logger.info('[RequestAvatarGenerationUseCase] Starting avatar generation request', { userId: input.userId, suitColor: input.suitColor, }); // Create the avatar generation request entity const requestId = uuidv4(); const request = AvatarGenerationRequest.create({ id: requestId, userId: input.userId, facePhotoUrl: input.facePhotoData, // Assuming facePhotoData is a URL or base64 suitColor: input.suitColor, style: input.style, }); // Save initial request await this.avatarRepo.save(request); // Present initial status presenter.present({ requestId, status: 'validating', }); // Validate face photo request.markAsValidating(); await this.avatarRepo.save(request); const validationResult = await this.faceValidation.validateFacePhoto(input.facePhotoData); if (!validationResult.isValid || !validationResult.hasFace || validationResult.faceCount !== 1) { const errorMessage = validationResult.errorMessage || 'Invalid face photo: must contain exactly one face'; request.fail(errorMessage); await this.avatarRepo.save(request); presenter.present({ requestId, status: 'failed', errorMessage, }); return; } // Generate avatars request.markAsGenerating(); await this.avatarRepo.save(request); const generationOptions = { facePhotoUrl: input.facePhotoData, prompt: request.buildPrompt(), suitColor: input.suitColor, style: input.style || 'realistic', count: 3, // Generate 3 avatar options }; const generationResult = await this.avatarGeneration.generateAvatars(generationOptions); if (!generationResult.success) { const errorMessage = generationResult.errorMessage || 'Failed to generate avatars'; request.fail(errorMessage); await this.avatarRepo.save(request); presenter.present({ requestId, status: 'failed', errorMessage, }); return; } // Complete the request const avatarUrls = generationResult.avatars.map(avatar => avatar.url); request.completeWithAvatars(avatarUrls); await this.avatarRepo.save(request); presenter.present({ requestId, status: 'completed', avatarUrls, }); this.logger.info('[RequestAvatarGenerationUseCase] Avatar generation completed', { requestId, userId: input.userId, avatarCount: avatarUrls.length, }); } catch (error) { this.logger.error('[RequestAvatarGenerationUseCase] Error during avatar generation', { error: error instanceof Error ? error.message : 'Unknown error', userId: input.userId, }); presenter.present({ requestId: uuidv4(), // Fallback ID status: 'failed', errorMessage: 'Internal error occurred during avatar generation', }); } } }