refactor
This commit is contained in:
26
apps/api/src/domain/media/MediaController.ts
Normal file
26
apps/api/src/domain/media/MediaController.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Controller, Post, Body, HttpStatus, Res } from '@nestjs/common';
|
||||
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { MediaService } from './MediaService';
|
||||
import { RequestAvatarGenerationInput, RequestAvatarGenerationOutput } from './dto/MediaDto'; // Assuming these DTOs are defined
|
||||
|
||||
@ApiTags('media')
|
||||
@Controller('media')
|
||||
export class MediaController {
|
||||
constructor(private readonly mediaService: MediaService) {}
|
||||
|
||||
@Post('avatar/generate')
|
||||
@ApiOperation({ summary: 'Request avatar generation' })
|
||||
@ApiResponse({ status: 201, description: 'Avatar generation request submitted', type: RequestAvatarGenerationOutput })
|
||||
async requestAvatarGeneration(
|
||||
@Body() input: RequestAvatarGenerationInput,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const result = await this.mediaService.requestAvatarGeneration(input);
|
||||
if (result.success) {
|
||||
res.status(HttpStatus.CREATED).json(result);
|
||||
} else {
|
||||
res.status(HttpStatus.BAD_REQUEST).json(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
apps/api/src/domain/media/MediaModule.ts
Normal file
11
apps/api/src/domain/media/MediaModule.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MediaService } from './MediaService';
|
||||
import { MediaController } from './MediaController';
|
||||
import { MediaProviders } from './MediaProviders';
|
||||
|
||||
@Module({
|
||||
controllers: [MediaController],
|
||||
providers: MediaProviders,
|
||||
exports: [MediaService],
|
||||
})
|
||||
export class MediaModule {}
|
||||
82
apps/api/src/domain/media/MediaProviders.ts
Normal file
82
apps/api/src/domain/media/MediaProviders.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { MediaService } from './MediaService';
|
||||
|
||||
// Import core interfaces
|
||||
import { IAvatarGenerationRepository } from '@core/media/domain/repositories/IAvatarGenerationRepository';
|
||||
import { FaceValidationPort } from '@core/media/application/ports/FaceValidationPort';
|
||||
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
// Import use cases
|
||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
|
||||
// Define injection tokens
|
||||
export const AVATAR_GENERATION_REPOSITORY_TOKEN = 'IAvatarGenerationRepository';
|
||||
export const FACE_VALIDATION_PORT_TOKEN = 'FaceValidationPort';
|
||||
export const AVATAR_GENERATION_PORT_TOKEN = 'AvatarGenerationPort';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
// Use case tokens
|
||||
export const REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN = 'RequestAvatarGenerationUseCase';
|
||||
|
||||
// Mock implementations
|
||||
class MockAvatarGenerationRepository implements IAvatarGenerationRepository {
|
||||
async save(_request: any): Promise<void> {}
|
||||
async findById(_id: string): Promise<any | null> { return null; }
|
||||
async findByUserId(_userId: string): Promise<any[]> { return []; }
|
||||
async findLatestByUserId(_userId: string): Promise<any | null> { return null; }
|
||||
async delete(_id: string): Promise<void> {}
|
||||
}
|
||||
|
||||
class MockFaceValidationAdapter implements FaceValidationPort {
|
||||
async validateFacePhoto(data: string): Promise<any> {
|
||||
return { isValid: true, hasFace: true, faceCount: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
class MockAvatarGenerationAdapter implements AvatarGenerationPort {
|
||||
async generateAvatars(options: any): Promise<any> {
|
||||
return {
|
||||
success: true,
|
||||
avatars: [
|
||||
{ url: 'https://cdn.example.com/avatars/mock-avatar-1.png' },
|
||||
{ url: 'https://cdn.example.com/avatars/mock-avatar-2.png' },
|
||||
{ url: 'https://cdn.example.com/avatars/mock-avatar-3.png' },
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MockLogger implements Logger {
|
||||
debug(message: string, meta?: any): void {}
|
||||
info(message: string, meta?: any): void {}
|
||||
warn(message: string, meta?: any): void {}
|
||||
error(message: string, error?: Error): void {}
|
||||
}
|
||||
|
||||
export const MediaProviders: Provider[] = [
|
||||
MediaService, // Provide the service itself
|
||||
{
|
||||
provide: AVATAR_GENERATION_REPOSITORY_TOKEN,
|
||||
useClass: MockAvatarGenerationRepository,
|
||||
},
|
||||
{
|
||||
provide: FACE_VALIDATION_PORT_TOKEN,
|
||||
useClass: MockFaceValidationAdapter,
|
||||
},
|
||||
{
|
||||
provide: AVATAR_GENERATION_PORT_TOKEN,
|
||||
useClass: MockAvatarGenerationAdapter,
|
||||
},
|
||||
{
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: MockLogger,
|
||||
},
|
||||
// Use cases
|
||||
{
|
||||
provide: REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN,
|
||||
useFactory: (avatarRepo: IAvatarGenerationRepository, faceValidation: FaceValidationPort, avatarGeneration: AvatarGenerationPort, logger: Logger) =>
|
||||
new RequestAvatarGenerationUseCase(avatarRepo, faceValidation, avatarGeneration, logger),
|
||||
inject: [AVATAR_GENERATION_REPOSITORY_TOKEN, FACE_VALIDATION_PORT_TOKEN, AVATAR_GENERATION_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
32
apps/api/src/domain/media/MediaService.ts
Normal file
32
apps/api/src/domain/media/MediaService.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { RequestAvatarGenerationInput, RequestAvatarGenerationOutput } from './dto/MediaDto';
|
||||
|
||||
// Use cases
|
||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
|
||||
// Presenters
|
||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||
|
||||
// Tokens
|
||||
import { REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN, LOGGER_TOKEN } from './MediaProviders';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
constructor(
|
||||
@Inject(REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN) private readonly requestAvatarGenerationUseCase: RequestAvatarGenerationUseCase,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async requestAvatarGeneration(input: RequestAvatarGenerationInput): Promise<RequestAvatarGenerationOutput> {
|
||||
this.logger.debug('[MediaService] Requesting avatar generation.');
|
||||
|
||||
const presenter = new RequestAvatarGenerationPresenter();
|
||||
await this.requestAvatarGenerationUseCase.execute({
|
||||
userId: input.userId,
|
||||
facePhotoData: input.facePhotoData,
|
||||
suitColor: input.suitColor as any,
|
||||
}, presenter);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
}
|
||||
40
apps/api/src/domain/media/dto/MediaDto.ts
Normal file
40
apps/api/src/domain/media/dto/MediaDto.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsBoolean } from 'class-validator';
|
||||
|
||||
export class RequestAvatarGenerationInput {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userId: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
facePhotoData: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
suitColor: string;
|
||||
}
|
||||
|
||||
export class RequestAvatarGenerationOutput {
|
||||
@ApiProperty({ type: Boolean })
|
||||
@IsBoolean()
|
||||
success: boolean;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsString()
|
||||
requestId?: string;
|
||||
|
||||
@ApiProperty({ type: [String], required: false })
|
||||
avatarUrls?: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsString()
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
// Assuming FacePhotoData and SuitColor are simple string types for DTO purposes
|
||||
export type FacePhotoData = string;
|
||||
export type SuitColor = string;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { RequestAvatarGenerationOutput } from '../dto/MediaDto';
|
||||
import type { IRequestAvatarGenerationPresenter, RequestAvatarGenerationResultDTO } from '@core/media/application/presenters/IRequestAvatarGenerationPresenter';
|
||||
|
||||
export class RequestAvatarGenerationPresenter implements IRequestAvatarGenerationPresenter {
|
||||
private result: RequestAvatarGenerationOutput | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
present(dto: RequestAvatarGenerationResultDTO) {
|
||||
this.result = {
|
||||
success: dto.status === 'completed',
|
||||
requestId: dto.requestId,
|
||||
avatarUrls: dto.avatarUrls,
|
||||
errorMessage: dto.errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): RequestAvatarGenerationOutput {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
|
||||
getViewModel(): RequestAvatarGenerationOutput {
|
||||
return this.viewModel;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user