module cleanup
This commit is contained in:
181
apps/api/src/domain/media/MediaController.test.ts
Normal file
181
apps/api/src/domain/media/MediaController.test.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { vi } from 'vitest';
|
||||
import { MediaController } from './MediaController';
|
||||
import { MediaService } from './MediaService';
|
||||
import type { Response } from 'express';
|
||||
import { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||
import { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
|
||||
|
||||
describe('MediaController', () => {
|
||||
let controller: MediaController;
|
||||
let service: ReturnType<typeof vi.mocked<MediaService>>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [MediaController],
|
||||
providers: [
|
||||
{
|
||||
provide: MediaService,
|
||||
useValue: {
|
||||
requestAvatarGeneration: vi.fn(),
|
||||
uploadMedia: vi.fn(),
|
||||
getMedia: vi.fn(),
|
||||
deleteMedia: vi.fn(),
|
||||
getAvatar: vi.fn(),
|
||||
updateAvatar: vi.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<MediaController>(MediaController);
|
||||
service = vi.mocked(module.get(MediaService));
|
||||
});
|
||||
|
||||
describe('requestAvatarGeneration', () => {
|
||||
it('should request avatar generation and return 201 on success', async () => {
|
||||
const input: RequestAvatarGenerationInputDTO = { driverId: 'driver-123' };
|
||||
const result = { success: true, jobId: 'job-123' };
|
||||
service.requestAvatarGeneration.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.requestAvatarGeneration(input, mockRes);
|
||||
|
||||
expect(service.requestAvatarGeneration).toHaveBeenCalledWith(input);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
|
||||
it('should return 400 on failure', async () => {
|
||||
const input: RequestAvatarGenerationInputDTO = { driverId: 'driver-123' };
|
||||
const result = { success: false, error: 'Error' };
|
||||
service.requestAvatarGeneration.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.requestAvatarGeneration(input, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadMedia', () => {
|
||||
it('should upload media and return 201 on success', async () => {
|
||||
const file: Express.Multer.File = { filename: 'file.jpg' } as Express.Multer.File;
|
||||
const input: UploadMediaInputDTO = { type: 'image' };
|
||||
const result = { success: true, mediaId: 'media-123' };
|
||||
service.uploadMedia.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.uploadMedia(file, input, mockRes);
|
||||
|
||||
expect(service.uploadMedia).toHaveBeenCalledWith({ ...input, file });
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMedia', () => {
|
||||
it('should return media if found', async () => {
|
||||
const mediaId = 'media-123';
|
||||
const result = { id: mediaId, url: 'url' };
|
||||
service.getMedia.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.getMedia(mediaId, mockRes);
|
||||
|
||||
expect(service.getMedia).toHaveBeenCalledWith(mediaId);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
|
||||
it('should return 404 if not found', async () => {
|
||||
const mediaId = 'media-123';
|
||||
service.getMedia.mockResolvedValue(null);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.getMedia(mediaId, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(404);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({ error: 'Media not found' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteMedia', () => {
|
||||
it('should delete media', async () => {
|
||||
const mediaId = 'media-123';
|
||||
const result = { success: true };
|
||||
service.deleteMedia.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.deleteMedia(mediaId, mockRes);
|
||||
|
||||
expect(service.deleteMedia).toHaveBeenCalledWith(mediaId);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvatar', () => {
|
||||
it('should return avatar if found', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const result = { url: 'avatar.jpg' };
|
||||
service.getAvatar.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.getAvatar(driverId, mockRes);
|
||||
|
||||
expect(service.getAvatar).toHaveBeenCalledWith(driverId);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAvatar', () => {
|
||||
it('should update avatar', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const input = { url: 'new-avatar.jpg' };
|
||||
const result = { success: true };
|
||||
service.updateAvatar.mockResolvedValue(result);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
|
||||
await controller.updateAvatar(driverId, input, mockRes);
|
||||
|
||||
expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,25 +3,19 @@ import { ApiTags, ApiResponse, ApiOperation, ApiParam, ApiConsumes } from '@nest
|
||||
import { Response } from 'express';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { MediaService } from './MediaService';
|
||||
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
|
||||
import type { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
|
||||
import type { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO';
|
||||
import type { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO';
|
||||
import type { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
|
||||
import type { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
|
||||
import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
|
||||
import type { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
|
||||
import { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||
import { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
|
||||
import { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
|
||||
import { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO';
|
||||
import { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO';
|
||||
import { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
|
||||
import { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
|
||||
import { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
|
||||
import { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
|
||||
|
||||
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
|
||||
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
|
||||
type UploadMediaInput = UploadMediaInputDTO;
|
||||
type UploadMediaOutput = UploadMediaOutputDTO;
|
||||
type GetMediaOutput = GetMediaOutputDTO;
|
||||
type DeleteMediaOutput = DeleteMediaOutputDTO;
|
||||
type GetAvatarOutput = GetAvatarOutputDTO;
|
||||
type UpdateAvatarInput = UpdateAvatarInputDTO;
|
||||
type UpdateAvatarOutput = UpdateAvatarOutputDTO;
|
||||
|
||||
@ApiTags('media')
|
||||
@Controller('media')
|
||||
@@ -30,7 +24,7 @@ export class MediaController {
|
||||
|
||||
@Post('avatar/generate')
|
||||
@ApiOperation({ summary: 'Request avatar generation' })
|
||||
@ApiResponse({ status: 201, description: 'Avatar generation request submitted', type: RequestAvatarGenerationOutput })
|
||||
@ApiResponse({ status: 201, description: 'Avatar generation request submitted', type: RequestAvatarGenerationOutputDTO })
|
||||
async requestAvatarGeneration(
|
||||
@Body() input: RequestAvatarGenerationInput,
|
||||
@Res() res: Response,
|
||||
@@ -47,7 +41,7 @@ export class MediaController {
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ApiOperation({ summary: 'Upload media file' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiResponse({ status: 201, description: 'Media uploaded successfully', type: UploadMediaOutput })
|
||||
@ApiResponse({ status: 201, description: 'Media uploaded successfully', type: UploadMediaOutputDTO })
|
||||
async uploadMedia(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Body() input: UploadMediaInput,
|
||||
@@ -64,7 +58,7 @@ export class MediaController {
|
||||
@Get(':mediaId')
|
||||
@ApiOperation({ summary: 'Get media by ID' })
|
||||
@ApiParam({ name: 'mediaId', description: 'Media ID' })
|
||||
@ApiResponse({ status: 200, description: 'Media details', type: GetMediaOutput })
|
||||
@ApiResponse({ status: 200, description: 'Media details', type: GetMediaOutputDTO })
|
||||
async getMedia(
|
||||
@Param('mediaId') mediaId: string,
|
||||
@Res() res: Response,
|
||||
@@ -80,7 +74,7 @@ export class MediaController {
|
||||
@Delete(':mediaId')
|
||||
@ApiOperation({ summary: 'Delete media by ID' })
|
||||
@ApiParam({ name: 'mediaId', description: 'Media ID' })
|
||||
@ApiResponse({ status: 200, description: 'Media deleted', type: DeleteMediaOutput })
|
||||
@ApiResponse({ status: 200, description: 'Media deleted', type: DeleteMediaOutputDTO })
|
||||
async deleteMedia(
|
||||
@Param('mediaId') mediaId: string,
|
||||
@Res() res: Response,
|
||||
@@ -92,7 +86,7 @@ export class MediaController {
|
||||
@Get('avatar/:driverId')
|
||||
@ApiOperation({ summary: 'Get avatar for driver' })
|
||||
@ApiParam({ name: 'driverId', description: 'Driver ID' })
|
||||
@ApiResponse({ status: 200, description: 'Avatar details', type: GetAvatarOutput })
|
||||
@ApiResponse({ status: 200, description: 'Avatar details', type: GetAvatarOutputDTO })
|
||||
async getAvatar(
|
||||
@Param('driverId') driverId: string,
|
||||
@Res() res: Response,
|
||||
@@ -108,7 +102,7 @@ export class MediaController {
|
||||
@Put('avatar/:driverId')
|
||||
@ApiOperation({ summary: 'Update avatar for driver' })
|
||||
@ApiParam({ name: 'driverId', description: 'Driver ID' })
|
||||
@ApiResponse({ status: 200, description: 'Avatar updated', type: UpdateAvatarOutput })
|
||||
@ApiResponse({ status: 200, description: 'Avatar updated', type: UpdateAvatarOutputDTO })
|
||||
async updateAvatar(
|
||||
@Param('driverId') driverId: string,
|
||||
@Body() input: UpdateAvatarInput,
|
||||
|
||||
30
apps/api/src/domain/media/MediaModule.test.ts
Normal file
30
apps/api/src/domain/media/MediaModule.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MediaModule } from './MediaModule';
|
||||
import { MediaController } from './MediaController';
|
||||
import { MediaService } from './MediaService';
|
||||
|
||||
describe('MediaModule', () => {
|
||||
let module: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
imports: [MediaModule],
|
||||
}).compile();
|
||||
});
|
||||
|
||||
it('should compile the module', () => {
|
||||
expect(module).toBeDefined();
|
||||
});
|
||||
|
||||
it('should provide MediaController', () => {
|
||||
const controller = module.get<MediaController>(MediaController);
|
||||
expect(controller).toBeDefined();
|
||||
expect(controller).toBeInstanceOf(MediaController);
|
||||
});
|
||||
|
||||
it('should provide MediaService', () => {
|
||||
const service = module.get<MediaService>(MediaService);
|
||||
expect(service).toBeDefined();
|
||||
expect(service).toBeInstanceOf(MediaService);
|
||||
});
|
||||
});
|
||||
@@ -3,39 +3,82 @@ import { MediaService } from './MediaService';
|
||||
|
||||
// Import core interfaces
|
||||
import { IAvatarGenerationRepository } from '@core/media/domain/repositories/IAvatarGenerationRepository';
|
||||
import { IMediaRepository } from '@core/media/domain/repositories/IMediaRepository';
|
||||
import { IAvatarRepository } from '@core/media/domain/repositories/IAvatarRepository';
|
||||
import { FaceValidationPort } from '@core/media/application/ports/FaceValidationPort';
|
||||
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
|
||||
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
|
||||
// Import use cases
|
||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
import { UploadMediaUseCase } from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||
import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||
import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
|
||||
// Define injection tokens
|
||||
export const AVATAR_GENERATION_REPOSITORY_TOKEN = 'IAvatarGenerationRepository';
|
||||
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
|
||||
export const AVATAR_REPOSITORY_TOKEN = 'IAvatarRepository';
|
||||
export const FACE_VALIDATION_PORT_TOKEN = 'FaceValidationPort';
|
||||
export const AVATAR_GENERATION_PORT_TOKEN = 'AvatarGenerationPort';
|
||||
export const MEDIA_STORAGE_PORT_TOKEN = 'MediaStoragePort';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
|
||||
// Use case tokens
|
||||
export const REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN = 'RequestAvatarGenerationUseCase';
|
||||
export const UPLOAD_MEDIA_USE_CASE_TOKEN = 'UploadMediaUseCase';
|
||||
export const GET_MEDIA_USE_CASE_TOKEN = 'GetMediaUseCase';
|
||||
export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
|
||||
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase';
|
||||
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
|
||||
|
||||
import type { AvatarGenerationRequest } from '@core/media/domain/entities/AvatarGenerationRequest';
|
||||
import type { Media } from '@core/media/domain/entities/Media';
|
||||
import type { Avatar } from '@core/media/domain/entities/Avatar';
|
||||
import type { FaceValidationResult } from '@core/media/application/ports/FaceValidationPort';
|
||||
import type { AvatarGenerationResult } from '@core/media/application/ports/AvatarGenerationPort';
|
||||
import type { UploadResult } from '@core/media/application/ports/MediaStoragePort';
|
||||
|
||||
// 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> {}
|
||||
async save(): Promise<void> {}
|
||||
async findById(): Promise<AvatarGenerationRequest | null> { return null; }
|
||||
async findByUserId(): Promise<AvatarGenerationRequest[]> { return []; }
|
||||
async findLatestByUserId(): Promise<AvatarGenerationRequest | null> { return null; }
|
||||
async delete(): Promise<void> {}
|
||||
}
|
||||
|
||||
class MockMediaRepository implements IMediaRepository {
|
||||
async save(): Promise<void> {}
|
||||
async findById(): Promise<Media | null> { return null; }
|
||||
async findByUploadedBy(): Promise<Media[]> { return []; }
|
||||
async delete(): Promise<void> {}
|
||||
}
|
||||
|
||||
class MockAvatarRepository implements IAvatarRepository {
|
||||
async save(): Promise<void> {}
|
||||
async findById(): Promise<Avatar | null> { return null; }
|
||||
async findActiveByDriverId(): Promise<Avatar | null> { return null; }
|
||||
async findByDriverId(): Promise<Avatar[]> { return []; }
|
||||
async delete(): Promise<void> {}
|
||||
}
|
||||
|
||||
class MockFaceValidationAdapter implements FaceValidationPort {
|
||||
async validateFacePhoto(data: string): Promise<any> {
|
||||
return { isValid: true, hasFace: true, faceCount: 1 };
|
||||
async validateFacePhoto(): Promise<FaceValidationResult> {
|
||||
return {
|
||||
isValid: true,
|
||||
hasFace: true,
|
||||
faceCount: 1,
|
||||
confidence: 0.95,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class MockAvatarGenerationAdapter implements AvatarGenerationPort {
|
||||
async generateAvatars(options: any): Promise<any> {
|
||||
async generateAvatars(): Promise<AvatarGenerationResult> {
|
||||
return {
|
||||
success: true,
|
||||
avatars: [
|
||||
@@ -47,11 +90,22 @@ class MockAvatarGenerationAdapter implements AvatarGenerationPort {
|
||||
}
|
||||
}
|
||||
|
||||
class MockMediaStorageAdapter implements MediaStoragePort {
|
||||
async uploadMedia(): Promise<UploadResult> {
|
||||
return {
|
||||
success: true,
|
||||
url: 'https://cdn.example.com/media/mock-file.png',
|
||||
filename: 'mock-file.png',
|
||||
};
|
||||
}
|
||||
async deleteMedia(): Promise<void> {}
|
||||
}
|
||||
|
||||
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 {}
|
||||
debug(): void {}
|
||||
info(): void {}
|
||||
warn(): void {}
|
||||
error(): void {}
|
||||
}
|
||||
|
||||
export const MediaProviders: Provider[] = [
|
||||
@@ -60,6 +114,14 @@ export const MediaProviders: Provider[] = [
|
||||
provide: AVATAR_GENERATION_REPOSITORY_TOKEN,
|
||||
useClass: MockAvatarGenerationRepository,
|
||||
},
|
||||
{
|
||||
provide: MEDIA_REPOSITORY_TOKEN,
|
||||
useClass: MockMediaRepository,
|
||||
},
|
||||
{
|
||||
provide: AVATAR_REPOSITORY_TOKEN,
|
||||
useClass: MockAvatarRepository,
|
||||
},
|
||||
{
|
||||
provide: FACE_VALIDATION_PORT_TOKEN,
|
||||
useClass: MockFaceValidationAdapter,
|
||||
@@ -68,6 +130,10 @@ export const MediaProviders: Provider[] = [
|
||||
provide: AVATAR_GENERATION_PORT_TOKEN,
|
||||
useClass: MockAvatarGenerationAdapter,
|
||||
},
|
||||
{
|
||||
provide: MEDIA_STORAGE_PORT_TOKEN,
|
||||
useClass: MockMediaStorageAdapter,
|
||||
},
|
||||
{
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: MockLogger,
|
||||
@@ -79,4 +145,34 @@ export const MediaProviders: Provider[] = [
|
||||
new RequestAvatarGenerationUseCase(avatarRepo, faceValidation, avatarGeneration, logger),
|
||||
inject: [AVATAR_GENERATION_REPOSITORY_TOKEN, FACE_VALIDATION_PORT_TOKEN, AVATAR_GENERATION_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPLOAD_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, logger: Logger) =>
|
||||
new UploadMediaUseCase(mediaRepo, mediaStorage, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaRepo: IMediaRepository, logger: Logger) =>
|
||||
new GetMediaUseCase(mediaRepo, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: DELETE_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaRepo: IMediaRepository, mediaStorage: MediaStoragePort, logger: Logger) =>
|
||||
new DeleteMediaUseCase(mediaRepo, mediaStorage, logger),
|
||||
inject: [MEDIA_REPOSITORY_TOKEN, MEDIA_STORAGE_PORT_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_AVATAR_USE_CASE_TOKEN,
|
||||
useFactory: (avatarRepo: IAvatarRepository, logger: Logger) =>
|
||||
new GetAvatarUseCase(avatarRepo, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||
useFactory: (avatarRepo: IAvatarRepository, logger: Logger) =>
|
||||
new UpdateAvatarUseCase(avatarRepo, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
|
||||
import type { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
|
||||
import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
|
||||
import type { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
|
||||
import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest';
|
||||
|
||||
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
|
||||
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
|
||||
@@ -21,18 +22,41 @@ type UpdateAvatarOutput = UpdateAvatarOutputDTO;
|
||||
|
||||
// Use cases
|
||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
import { UploadMediaUseCase } from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||
import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||
import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
|
||||
// Presenters
|
||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||
import { UploadMediaPresenter } from './presenters/UploadMediaPresenter';
|
||||
import { GetMediaPresenter } from './presenters/GetMediaPresenter';
|
||||
import { DeleteMediaPresenter } from './presenters/DeleteMediaPresenter';
|
||||
import { GetAvatarPresenter } from './presenters/GetAvatarPresenter';
|
||||
import { UpdateAvatarPresenter } from './presenters/UpdateAvatarPresenter';
|
||||
|
||||
// Tokens
|
||||
import { REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN, LOGGER_TOKEN } from './MediaProviders';
|
||||
import {
|
||||
REQUEST_AVATAR_GENERATION_USE_CASE_TOKEN,
|
||||
UPLOAD_MEDIA_USE_CASE_TOKEN,
|
||||
GET_MEDIA_USE_CASE_TOKEN,
|
||||
DELETE_MEDIA_USE_CASE_TOKEN,
|
||||
GET_AVATAR_USE_CASE_TOKEN,
|
||||
UPDATE_AVATAR_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(UPLOAD_MEDIA_USE_CASE_TOKEN) private readonly uploadMediaUseCase: UploadMediaUseCase,
|
||||
@Inject(GET_MEDIA_USE_CASE_TOKEN) private readonly getMediaUseCase: GetMediaUseCase,
|
||||
@Inject(DELETE_MEDIA_USE_CASE_TOKEN) private readonly deleteMediaUseCase: DeleteMediaUseCase,
|
||||
@Inject(GET_AVATAR_USE_CASE_TOKEN) private readonly getAvatarUseCase: GetAvatarUseCase,
|
||||
@Inject(UPDATE_AVATAR_USE_CASE_TOKEN) private readonly updateAvatarUseCase: UpdateAvatarUseCase,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -43,46 +67,118 @@ export class MediaService {
|
||||
await this.requestAvatarGenerationUseCase.execute({
|
||||
userId: input.userId,
|
||||
facePhotoData: input.facePhotoData,
|
||||
suitColor: input.suitColor as any,
|
||||
suitColor: input.suitColor as RacingSuitColor,
|
||||
}, presenter);
|
||||
return presenter.viewModel;
|
||||
}
|
||||
|
||||
async uploadMedia(input: UploadMediaInput & { file: Express.Multer.File }): Promise<UploadMediaOutput> {
|
||||
this.logger.debug('[MediaService] Uploading media.');
|
||||
// TODO: Implement media upload logic
|
||||
return {
|
||||
success: true,
|
||||
mediaId: 'placeholder-media-id',
|
||||
url: 'placeholder-url',
|
||||
};
|
||||
|
||||
const presenter = new UploadMediaPresenter();
|
||||
|
||||
await this.uploadMediaUseCase.execute({
|
||||
file: input.file,
|
||||
uploadedBy: input.userId, // Assuming userId is the uploader
|
||||
metadata: input.metadata,
|
||||
}, presenter);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
if (result.success) {
|
||||
return {
|
||||
success: true,
|
||||
mediaId: result.mediaId!,
|
||||
url: result.url!,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
errorMessage: result.errorMessage || 'Upload failed',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getMedia(mediaId: string): Promise<GetMediaOutput | null> {
|
||||
this.logger.debug(`[MediaService] Getting media: ${mediaId}`);
|
||||
// TODO: Implement get media logic
|
||||
|
||||
const presenter = new GetMediaPresenter();
|
||||
|
||||
await this.getMediaUseCase.execute({ mediaId }, presenter);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
if (result.success && result.media) {
|
||||
return {
|
||||
success: true,
|
||||
mediaId: result.media.id,
|
||||
filename: result.media.filename,
|
||||
originalName: result.media.originalName,
|
||||
mimeType: result.media.mimeType,
|
||||
size: result.media.size,
|
||||
url: result.media.url,
|
||||
type: result.media.type,
|
||||
uploadedBy: result.media.uploadedBy,
|
||||
uploadedAt: result.media.uploadedAt,
|
||||
metadata: result.media.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaOutput> {
|
||||
this.logger.debug(`[MediaService] Deleting media: ${mediaId}`);
|
||||
// TODO: Implement delete media logic
|
||||
|
||||
const presenter = new DeleteMediaPresenter();
|
||||
|
||||
await this.deleteMediaUseCase.execute({ mediaId }, presenter);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
success: result.success,
|
||||
errorMessage: result.errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
async getAvatar(driverId: string): Promise<GetAvatarOutput | null> {
|
||||
this.logger.debug(`[MediaService] Getting avatar for driver: ${driverId}`);
|
||||
// TODO: Implement get avatar logic
|
||||
|
||||
const presenter = new GetAvatarPresenter();
|
||||
|
||||
await this.getAvatarUseCase.execute({ driverId }, presenter);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
if (result.success && result.avatar) {
|
||||
return {
|
||||
success: true,
|
||||
avatarId: result.avatar.id,
|
||||
driverId: result.avatar.driverId,
|
||||
mediaUrl: result.avatar.mediaUrl,
|
||||
selectedAt: result.avatar.selectedAt,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutput> {
|
||||
this.logger.debug(`[MediaService] Updating avatar for driver: ${driverId}`);
|
||||
// TODO: Implement update avatar logic
|
||||
|
||||
const presenter = new UpdateAvatarPresenter();
|
||||
|
||||
await this.updateAvatarUseCase.execute({
|
||||
driverId,
|
||||
mediaUrl: input.mediaUrl,
|
||||
}, presenter);
|
||||
|
||||
const result = presenter.viewModel;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
success: result.success,
|
||||
errorMessage: result.errorMessage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IsString, IsOptional } from 'class-validator';
|
||||
|
||||
export class UploadMediaInputDTO {
|
||||
@ApiProperty({ type: 'string', format: 'binary' })
|
||||
file: any; // File upload handled by multer
|
||||
file: Express.Multer.File;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
|
||||
14
apps/api/src/domain/media/presenters/DeleteMediaPresenter.ts
Normal file
14
apps/api/src/domain/media/presenters/DeleteMediaPresenter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { IDeleteMediaPresenter, DeleteMediaResult } from '@core/media/application/presenters/IDeleteMediaPresenter';
|
||||
|
||||
export class DeleteMediaPresenter implements IDeleteMediaPresenter {
|
||||
private result: DeleteMediaResult | null = null;
|
||||
|
||||
present(result: DeleteMediaResult) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
get viewModel(): DeleteMediaResult {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
14
apps/api/src/domain/media/presenters/GetAvatarPresenter.ts
Normal file
14
apps/api/src/domain/media/presenters/GetAvatarPresenter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { IGetAvatarPresenter, GetAvatarResult } from '@core/media/application/presenters/IGetAvatarPresenter';
|
||||
|
||||
export class GetAvatarPresenter implements IGetAvatarPresenter {
|
||||
private result: GetAvatarResult | null = null;
|
||||
|
||||
present(result: GetAvatarResult) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
get viewModel(): GetAvatarResult {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
14
apps/api/src/domain/media/presenters/GetMediaPresenter.ts
Normal file
14
apps/api/src/domain/media/presenters/GetMediaPresenter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { IGetMediaPresenter, GetMediaResult } from '@core/media/application/presenters/IGetMediaPresenter';
|
||||
|
||||
export class GetMediaPresenter implements IGetMediaPresenter {
|
||||
private result: GetMediaResult | null = null;
|
||||
|
||||
present(result: GetMediaResult) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
get viewModel(): GetMediaResult {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { RequestAvatarGenerationOutput } from '../dto/MediaDto';
|
||||
import type { RequestAvatarGenerationOutputDTO } from '../dtos/RequestAvatarGenerationOutputDTO';
|
||||
import type { IRequestAvatarGenerationPresenter, RequestAvatarGenerationResultDTO } from '@core/media/application/presenters/IRequestAvatarGenerationPresenter';
|
||||
|
||||
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
|
||||
|
||||
export class RequestAvatarGenerationPresenter implements IRequestAvatarGenerationPresenter {
|
||||
private result: RequestAvatarGenerationOutput | null = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { IUpdateAvatarPresenter, UpdateAvatarResult } from '@core/media/application/presenters/IUpdateAvatarPresenter';
|
||||
|
||||
export class UpdateAvatarPresenter implements IUpdateAvatarPresenter {
|
||||
private result: UpdateAvatarResult | null = null;
|
||||
|
||||
present(result: UpdateAvatarResult) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
get viewModel(): UpdateAvatarResult {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
14
apps/api/src/domain/media/presenters/UploadMediaPresenter.ts
Normal file
14
apps/api/src/domain/media/presenters/UploadMediaPresenter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { IUploadMediaPresenter, UploadMediaResult } from '@core/media/application/presenters/IUploadMediaPresenter';
|
||||
|
||||
export class UploadMediaPresenter implements IUploadMediaPresenter {
|
||||
private result: UploadMediaResult | null = null;
|
||||
|
||||
present(result: UploadMediaResult) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
get viewModel(): UploadMediaResult {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user