refactor
This commit is contained in:
@@ -5,10 +5,16 @@ import { MediaService } from './MediaService';
|
||||
import type { Response } from 'express';
|
||||
import { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||
import { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
|
||||
import { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
|
||||
import { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO';
|
||||
import { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO';
|
||||
import { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
|
||||
import { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
|
||||
import { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
|
||||
|
||||
describe('MediaController', () => {
|
||||
let controller: MediaController;
|
||||
let service: ReturnType<typeof vi.mocked<MediaService>>;
|
||||
let service: jest.Mocked<MediaService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -29,153 +35,201 @@ describe('MediaController', () => {
|
||||
}).compile();
|
||||
|
||||
controller = module.get<MediaController>(MediaController);
|
||||
service = vi.mocked(module.get(MediaService));
|
||||
service = module.get(MediaService) as jest.Mocked<MediaService>;
|
||||
});
|
||||
|
||||
const createMockResponse = (): Response => {
|
||||
const res: Partial<Response> = {};
|
||||
res.status = vi.fn().mockReturnValue(res as Response);
|
||||
res.json = vi.fn().mockReturnValue(res as Response);
|
||||
return res as Response;
|
||||
};
|
||||
|
||||
describe('requestAvatarGeneration', () => {
|
||||
it('should request avatar generation and return 201 on success', async () => {
|
||||
const input: RequestAvatarGenerationInputDTO = { driverId: 'driver-123' };
|
||||
const viewModel = { success: true, jobId: 'job-123' } as any;
|
||||
service.requestAvatarGeneration.mockResolvedValue({ viewModel } as any);
|
||||
const input: RequestAvatarGenerationInputDTO = {
|
||||
userId: 'user-123',
|
||||
facePhotoData: 'photo-data',
|
||||
suitColor: 'red',
|
||||
};
|
||||
const dto: RequestAvatarGenerationOutputDTO = {
|
||||
success: true,
|
||||
requestId: 'req-123',
|
||||
avatarUrls: ['https://example.com/avatar.png'],
|
||||
};
|
||||
service.requestAvatarGeneration.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.requestAvatarGeneration(input, mockRes);
|
||||
await controller.requestAvatarGeneration(input, res);
|
||||
|
||||
expect(service.requestAvatarGeneration).toHaveBeenCalledWith(input);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(viewModel);
|
||||
expect(res.status).toHaveBeenCalledWith(201);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
|
||||
it('should return 400 on failure', async () => {
|
||||
const input: RequestAvatarGenerationInputDTO = { driverId: 'driver-123' };
|
||||
const viewModel = { success: false, error: 'Error' } as any;
|
||||
service.requestAvatarGeneration.mockResolvedValue({ viewModel } as any);
|
||||
const input: RequestAvatarGenerationInputDTO = {
|
||||
userId: 'user-123',
|
||||
facePhotoData: 'photo-data',
|
||||
suitColor: 'red',
|
||||
};
|
||||
const dto: RequestAvatarGenerationOutputDTO = {
|
||||
success: false,
|
||||
errorMessage: 'Error',
|
||||
};
|
||||
service.requestAvatarGeneration.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.requestAvatarGeneration(input, mockRes);
|
||||
await controller.requestAvatarGeneration(input, res);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(viewModel);
|
||||
expect(service.requestAvatarGeneration).toHaveBeenCalledWith(input);
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
});
|
||||
|
||||
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 viewModel = { success: true, mediaId: 'media-123' } as any;
|
||||
service.uploadMedia.mockResolvedValue({ viewModel } as any);
|
||||
const input: UploadMediaInputDTO = { type: 'image' } as UploadMediaInputDTO;
|
||||
const dto: UploadMediaOutputDTO = {
|
||||
success: true,
|
||||
mediaId: 'media-123',
|
||||
url: 'https://example.com/file.jpg',
|
||||
};
|
||||
service.uploadMedia.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.uploadMedia(file, input, mockRes);
|
||||
await controller.uploadMedia(file, input, res);
|
||||
|
||||
expect(service.uploadMedia).toHaveBeenCalledWith({ ...input, file });
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(viewModel);
|
||||
expect(res.status).toHaveBeenCalledWith(201);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
|
||||
it('should return 400 when upload fails', async () => {
|
||||
const file: Express.Multer.File = { filename: 'file.jpg' } as Express.Multer.File;
|
||||
const input: UploadMediaInputDTO = { type: 'image' } as UploadMediaInputDTO;
|
||||
const dto: UploadMediaOutputDTO = {
|
||||
success: false,
|
||||
error: 'Upload failed',
|
||||
};
|
||||
service.uploadMedia.mockResolvedValue(dto);
|
||||
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.uploadMedia(file, input, res);
|
||||
|
||||
expect(service.uploadMedia).toHaveBeenCalledWith({ ...input, file });
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMedia', () => {
|
||||
it('should return media if found', async () => {
|
||||
const mediaId = 'media-123';
|
||||
const viewModel = { id: mediaId, url: 'url' } as any;
|
||||
service.getMedia.mockResolvedValue({ viewModel } as any);
|
||||
const dto: GetMediaOutputDTO = {
|
||||
id: mediaId,
|
||||
url: 'https://example.com/file.jpg',
|
||||
type: 'image',
|
||||
category: 'avatar',
|
||||
uploadedAt: new Date(),
|
||||
size: 123,
|
||||
};
|
||||
service.getMedia.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.getMedia(mediaId, mockRes);
|
||||
await controller.getMedia(mediaId, res);
|
||||
|
||||
expect(service.getMedia).toHaveBeenCalledWith(mediaId);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(viewModel);
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
|
||||
it('should return 404 if not found', async () => {
|
||||
const mediaId = 'media-123';
|
||||
service.getMedia.mockResolvedValue({ viewModel: null } as any);
|
||||
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>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.getMedia(mediaId, mockRes);
|
||||
await controller.getMedia(mediaId, res);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(404);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({ error: 'Media not found' });
|
||||
expect(service.getMedia).toHaveBeenCalledWith(mediaId);
|
||||
expect(res.status).toHaveBeenCalledWith(404);
|
||||
expect(res.json).toHaveBeenCalledWith({ error: 'Media not found' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteMedia', () => {
|
||||
it('should delete media', async () => {
|
||||
it('should delete media and return result', async () => {
|
||||
const mediaId = 'media-123';
|
||||
const viewModel = { success: true } as any;
|
||||
service.deleteMedia.mockResolvedValue({ viewModel } as any);
|
||||
const dto: DeleteMediaOutputDTO = {
|
||||
success: true,
|
||||
};
|
||||
service.deleteMedia.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.deleteMedia(mediaId, mockRes);
|
||||
await controller.deleteMedia(mediaId, res);
|
||||
|
||||
expect(service.deleteMedia).toHaveBeenCalledWith(mediaId);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(viewModel);
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvatar', () => {
|
||||
it('should return avatar if found', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const result = { url: 'avatar.jpg' };
|
||||
service.getAvatar.mockResolvedValue(result);
|
||||
const dto: GetAvatarOutputDTO = {
|
||||
avatarUrl: 'https://example.com/avatar.png',
|
||||
};
|
||||
service.getAvatar.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.getAvatar(driverId, mockRes);
|
||||
await controller.getAvatar(driverId, res);
|
||||
|
||||
expect(service.getAvatar).toHaveBeenCalledWith(driverId);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
|
||||
it('should return 404 when avatar not found', async () => {
|
||||
const driverId = 'driver-123';
|
||||
service.getAvatar.mockResolvedValue(null);
|
||||
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.getAvatar(driverId, res);
|
||||
|
||||
expect(service.getAvatar).toHaveBeenCalledWith(driverId);
|
||||
expect(res.status).toHaveBeenCalledWith(404);
|
||||
expect(res.json).toHaveBeenCalledWith({ error: 'Avatar not found' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAvatar', () => {
|
||||
it('should update avatar', async () => {
|
||||
it('should update avatar and return result', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const input = { url: 'new-avatar.jpg' };
|
||||
const result = { success: true };
|
||||
service.updateAvatar.mockResolvedValue(result);
|
||||
const input = { mediaUrl: 'https://example.com/new-avatar.png' } as UpdateAvatarOutputDTO;
|
||||
const dto: UpdateAvatarOutputDTO = {
|
||||
success: true,
|
||||
};
|
||||
service.updateAvatar.mockResolvedValue(dto);
|
||||
|
||||
const mockRes: ReturnType<typeof vi.mocked<Response>> = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
} as unknown as ReturnType<typeof vi.mocked<Response>>;
|
||||
const res = createMockResponse();
|
||||
|
||||
await controller.updateAvatar(driverId, input, mockRes);
|
||||
await controller.updateAvatar(driverId, input as any, res);
|
||||
|
||||
expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(result);
|
||||
expect(service.updateAvatar).toHaveBeenCalledWith(driverId, input as any);
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
expect(res.json).toHaveBeenCalledWith(dto);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,13 +29,12 @@ export class MediaController {
|
||||
@Body() input: RequestAvatarGenerationInput,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.mediaService.requestAvatarGeneration(input);
|
||||
const viewModel = presenter.viewModel;
|
||||
const dto: RequestAvatarGenerationOutputDTO = await this.mediaService.requestAvatarGeneration(input);
|
||||
|
||||
if (viewModel.success) {
|
||||
res.status(HttpStatus.CREATED).json(viewModel);
|
||||
if (dto.success) {
|
||||
res.status(HttpStatus.CREATED).json(dto);
|
||||
} else {
|
||||
res.status(HttpStatus.BAD_REQUEST).json(viewModel);
|
||||
res.status(HttpStatus.BAD_REQUEST).json(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +48,12 @@ export class MediaController {
|
||||
@Body() input: UploadMediaInput,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.mediaService.uploadMedia({ ...input, file });
|
||||
const viewModel = presenter.viewModel;
|
||||
const dto: UploadMediaOutputDTO = await this.mediaService.uploadMedia({ ...input, file });
|
||||
|
||||
if (viewModel.success) {
|
||||
res.status(HttpStatus.CREATED).json(viewModel);
|
||||
if (dto.success) {
|
||||
res.status(HttpStatus.CREATED).json(dto);
|
||||
} else {
|
||||
res.status(HttpStatus.BAD_REQUEST).json(viewModel);
|
||||
res.status(HttpStatus.BAD_REQUEST).json(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +65,10 @@ export class MediaController {
|
||||
@Param('mediaId') mediaId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.mediaService.getMedia(mediaId);
|
||||
const viewModel = presenter.viewModel;
|
||||
const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId);
|
||||
|
||||
if (viewModel) {
|
||||
res.status(HttpStatus.OK).json(viewModel);
|
||||
if (dto) {
|
||||
res.status(HttpStatus.OK).json(dto);
|
||||
} else {
|
||||
res.status(HttpStatus.NOT_FOUND).json({ error: 'Media not found' });
|
||||
}
|
||||
@@ -85,10 +82,9 @@ export class MediaController {
|
||||
@Param('mediaId') mediaId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.mediaService.deleteMedia(mediaId);
|
||||
const viewModel = presenter.viewModel;
|
||||
const dto: DeleteMediaOutputDTO = await this.mediaService.deleteMedia(mediaId);
|
||||
|
||||
res.status(HttpStatus.OK).json(viewModel);
|
||||
res.status(HttpStatus.OK).json(dto);
|
||||
}
|
||||
|
||||
@Get('avatar/:driverId')
|
||||
@@ -99,11 +95,10 @@ export class MediaController {
|
||||
@Param('driverId') driverId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.mediaService.getAvatar(driverId);
|
||||
const viewModel = presenter.viewModel;
|
||||
const dto: GetAvatarOutputDTO | null = await this.mediaService.getAvatar(driverId);
|
||||
|
||||
if (viewModel) {
|
||||
res.status(HttpStatus.OK).json(viewModel);
|
||||
if (dto) {
|
||||
res.status(HttpStatus.OK).json(dto);
|
||||
} else {
|
||||
res.status(HttpStatus.NOT_FOUND).json({ error: 'Avatar not found' });
|
||||
}
|
||||
@@ -118,9 +113,8 @@ export class MediaController {
|
||||
@Body() input: UpdateAvatarInput,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const presenter = await this.mediaService.updateAvatar(driverId, input);
|
||||
const viewModel = presenter.viewModel;
|
||||
const dto: UpdateAvatarOutputDTO = await this.mediaService.updateAvatar(driverId, input);
|
||||
|
||||
res.status(HttpStatus.OK).json(viewModel);
|
||||
res.status(HttpStatus.OK).json(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@ import { Injectable, Inject } from '@nestjs/common';
|
||||
import type { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
|
||||
import type { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
|
||||
import type { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
|
||||
import type { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
|
||||
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 { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
|
||||
import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest';
|
||||
|
||||
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
|
||||
@@ -32,89 +38,116 @@ import {
|
||||
DELETE_MEDIA_USE_CASE_TOKEN,
|
||||
GET_AVATAR_USE_CASE_TOKEN,
|
||||
UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||
LOGGER_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,
|
||||
@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,
|
||||
) {}
|
||||
|
||||
async requestAvatarGeneration(input: RequestAvatarGenerationInput): Promise<RequestAvatarGenerationPresenter> {
|
||||
async requestAvatarGeneration(
|
||||
input: RequestAvatarGenerationInput,
|
||||
): Promise<RequestAvatarGenerationOutputDTO> {
|
||||
this.logger.debug('[MediaService] Requesting avatar generation.');
|
||||
|
||||
const presenter = new RequestAvatarGenerationPresenter();
|
||||
await this.requestAvatarGenerationUseCase.execute({
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.requestAvatarGenerationUseCase.execute({
|
||||
userId: input.userId,
|
||||
facePhotoData: input.facePhotoData,
|
||||
suitColor: input.suitColor as RacingSuitColor,
|
||||
}, presenter);
|
||||
});
|
||||
|
||||
return presenter;
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
}
|
||||
|
||||
async uploadMedia(input: UploadMediaInput & { file: Express.Multer.File }): Promise<UploadMediaPresenter> {
|
||||
async uploadMedia(
|
||||
input: UploadMediaInput & { file: Express.Multer.File } & { userId?: string; metadata?: Record<string, any> },
|
||||
): Promise<UploadMediaOutputDTO> {
|
||||
this.logger.debug('[MediaService] Uploading media.');
|
||||
|
||||
const presenter = new UploadMediaPresenter();
|
||||
presenter.reset();
|
||||
|
||||
await this.uploadMediaUseCase.execute({
|
||||
const result = await this.uploadMediaUseCase.execute({
|
||||
file: input.file,
|
||||
uploadedBy: input.userId, // Assuming userId is the uploader
|
||||
uploadedBy: input.userId ?? '',
|
||||
metadata: input.metadata,
|
||||
}, presenter);
|
||||
});
|
||||
|
||||
return presenter;
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
}
|
||||
|
||||
async getMedia(mediaId: string): Promise<GetMediaPresenter> {
|
||||
async getMedia(mediaId: string): Promise<GetMediaOutputDTO | null> {
|
||||
this.logger.debug(`[MediaService] Getting media: ${mediaId}`);
|
||||
|
||||
const presenter = new GetMediaPresenter();
|
||||
presenter.reset();
|
||||
|
||||
await this.getMediaUseCase.execute({ mediaId }, presenter);
|
||||
const result = await this.getMediaUseCase.execute({ mediaId });
|
||||
presenter.present(result);
|
||||
|
||||
return presenter;
|
||||
return presenter.responseModel;
|
||||
}
|
||||
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaPresenter> {
|
||||
async deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
|
||||
this.logger.debug(`[MediaService] Deleting media: ${mediaId}`);
|
||||
|
||||
const presenter = new DeleteMediaPresenter();
|
||||
presenter.reset();
|
||||
|
||||
await this.deleteMediaUseCase.execute({ mediaId }, presenter);
|
||||
const result = await this.deleteMediaUseCase.execute({ mediaId });
|
||||
presenter.present(result);
|
||||
|
||||
return presenter;
|
||||
return presenter.responseModel;
|
||||
}
|
||||
|
||||
async getAvatar(driverId: string): Promise<GetAvatarPresenter> {
|
||||
async getAvatar(driverId: string): Promise<GetAvatarOutputDTO | null> {
|
||||
this.logger.debug(`[MediaService] Getting avatar for driver: ${driverId}`);
|
||||
|
||||
const presenter = new GetAvatarPresenter();
|
||||
presenter.reset();
|
||||
|
||||
await this.getAvatarUseCase.execute({ driverId }, presenter);
|
||||
const result = await this.getAvatarUseCase.execute({ driverId });
|
||||
presenter.present(result);
|
||||
|
||||
return presenter;
|
||||
return presenter.responseModel;
|
||||
}
|
||||
|
||||
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarPresenter> {
|
||||
async updateAvatar(driverId: string, input: UpdateAvatarInput): Promise<UpdateAvatarOutputDTO> {
|
||||
this.logger.debug(`[MediaService] Updating avatar for driver: ${driverId}`);
|
||||
|
||||
|
||||
const presenter = new UpdateAvatarPresenter();
|
||||
|
||||
await this.updateAvatarUseCase.execute({
|
||||
presenter.reset();
|
||||
|
||||
const result = await this.updateAvatarUseCase.execute({
|
||||
driverId,
|
||||
mediaUrl: input.mediaUrl,
|
||||
}, presenter);
|
||||
|
||||
return presenter;
|
||||
});
|
||||
|
||||
presenter.present(result);
|
||||
|
||||
return presenter.responseModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
import type { IDeleteMediaPresenter, DeleteMediaResult } from '@core/media/application/presenters/IDeleteMediaPresenter';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
DeleteMediaResult,
|
||||
DeleteMediaErrorCode,
|
||||
} from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import type { DeleteMediaOutputDTO } from '../dtos/DeleteMediaOutputDTO';
|
||||
|
||||
type DeleteMediaOutput = DeleteMediaOutputDTO;
|
||||
type DeleteMediaResponseModel = DeleteMediaOutputDTO;
|
||||
|
||||
export class DeleteMediaPresenter implements IDeleteMediaPresenter {
|
||||
private result: DeleteMediaResult | null = null;
|
||||
export type DeleteMediaApplicationError = ApplicationErrorCode<
|
||||
DeleteMediaErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
present(result: DeleteMediaResult) {
|
||||
this.result = result;
|
||||
export class DeleteMediaPresenter {
|
||||
private model: DeleteMediaResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
get viewModel(): DeleteMediaOutput {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
present(result: Result<DeleteMediaResult, DeleteMediaApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
return {
|
||||
success: this.result.success,
|
||||
error: this.result.errorMessage,
|
||||
this.model = {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Failed to delete media',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
success: output.deleted,
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getResponseModel(): DeleteMediaResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): DeleteMediaResponseModel {
|
||||
if (!this.model) throw new Error('Presenter not presented');
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,49 @@
|
||||
import type { IGetAvatarPresenter, GetAvatarResult } from '@core/media/application/presenters/IGetAvatarPresenter';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
GetAvatarResult,
|
||||
GetAvatarErrorCode,
|
||||
} from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import type { GetAvatarOutputDTO } from '../dtos/GetAvatarOutputDTO';
|
||||
|
||||
export type GetAvatarViewModel = GetAvatarOutputDTO | null;
|
||||
export type GetAvatarResponseModel = GetAvatarOutputDTO | null;
|
||||
|
||||
export class GetAvatarPresenter implements IGetAvatarPresenter {
|
||||
private result: GetAvatarResult | null = null;
|
||||
export type GetAvatarApplicationError = ApplicationErrorCode<
|
||||
GetAvatarErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
present(result: GetAvatarResult) {
|
||||
this.result = result;
|
||||
export class GetAvatarPresenter {
|
||||
private model: GetAvatarResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
get viewModel(): GetAvatarViewModel {
|
||||
if (!this.result || !this.result.success || !this.result.avatar) {
|
||||
return null;
|
||||
present(result: Result<GetAvatarResult, GetAvatarApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
if (error.code === 'AVATAR_NOT_FOUND') {
|
||||
this.model = null;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(error.details?.message ?? 'Failed to get avatar');
|
||||
}
|
||||
|
||||
return {
|
||||
avatarUrl: this.result.avatar.mediaUrl,
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
avatarUrl: output.avatar.mediaUrl,
|
||||
};
|
||||
}
|
||||
|
||||
getResponseModel(): GetAvatarResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): GetAvatarResponseModel {
|
||||
return this.model ?? null;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,39 @@
|
||||
import type { IGetMediaPresenter, GetMediaResult } from '@core/media/application/presenters/IGetMediaPresenter';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { GetMediaResult, GetMediaErrorCode } from '@core/media/application/use-cases/GetMediaUseCase';
|
||||
import type { GetMediaOutputDTO } from '../dtos/GetMediaOutputDTO';
|
||||
|
||||
// The HTTP-facing DTO (or null when not found)
|
||||
export type GetMediaViewModel = GetMediaOutputDTO | null;
|
||||
export type GetMediaResponseModel = GetMediaOutputDTO | null;
|
||||
|
||||
export class GetMediaPresenter implements IGetMediaPresenter {
|
||||
private result: GetMediaResult | null = null;
|
||||
export type GetMediaApplicationError = ApplicationErrorCode<
|
||||
GetMediaErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
present(result: GetMediaResult) {
|
||||
this.result = result;
|
||||
export class GetMediaPresenter {
|
||||
private model: GetMediaResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
get viewModel(): GetMediaViewModel {
|
||||
if (!this.result || !this.result.success || !this.result.media) {
|
||||
return null;
|
||||
present(result: Result<GetMediaResult, GetMediaApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
if (error.code === 'MEDIA_NOT_FOUND') {
|
||||
this.model = null;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(error.details?.message ?? 'Failed to get media');
|
||||
}
|
||||
|
||||
const media = this.result.media;
|
||||
const output = result.unwrap();
|
||||
|
||||
return {
|
||||
const media = output.media;
|
||||
|
||||
this.model = {
|
||||
id: media.id,
|
||||
url: media.url,
|
||||
type: media.type,
|
||||
@@ -28,4 +43,12 @@ export class GetMediaPresenter implements IGetMediaPresenter {
|
||||
size: media.size,
|
||||
};
|
||||
}
|
||||
|
||||
getResponseModel(): GetMediaResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): GetMediaResponseModel {
|
||||
return this.model ?? null;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,59 @@
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
RequestAvatarGenerationResult,
|
||||
RequestAvatarGenerationErrorCode,
|
||||
} from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
import type { RequestAvatarGenerationOutputDTO } from '../dtos/RequestAvatarGenerationOutputDTO';
|
||||
import type { IRequestAvatarGenerationPresenter, RequestAvatarGenerationResultDTO } from '@core/media/application/presenters/IRequestAvatarGenerationPresenter';
|
||||
|
||||
type RequestAvatarGenerationOutput = RequestAvatarGenerationOutputDTO;
|
||||
type RequestAvatarGenerationResponseModel = RequestAvatarGenerationOutputDTO;
|
||||
|
||||
export class RequestAvatarGenerationPresenter implements IRequestAvatarGenerationPresenter {
|
||||
private result: RequestAvatarGenerationOutput | null = null;
|
||||
export type RequestAvatarGenerationApplicationError = ApplicationErrorCode<
|
||||
RequestAvatarGenerationErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class RequestAvatarGenerationPresenter {
|
||||
private model: RequestAvatarGenerationResponseModel | null = null;
|
||||
|
||||
reset() {
|
||||
this.result = null;
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(dto: RequestAvatarGenerationResultDTO) {
|
||||
this.result = {
|
||||
success: dto.status === 'completed',
|
||||
requestId: dto.requestId,
|
||||
avatarUrls: dto.avatarUrls,
|
||||
errorMessage: dto.errorMessage,
|
||||
present(
|
||||
result: Result<
|
||||
RequestAvatarGenerationResult,
|
||||
RequestAvatarGenerationApplicationError
|
||||
>,
|
||||
): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
this.model = {
|
||||
success: false,
|
||||
requestId: '',
|
||||
avatarUrls: [],
|
||||
errorMessage: error.details?.message ?? 'Failed to request avatar generation',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
success: output.status === 'completed',
|
||||
requestId: output.requestId,
|
||||
avatarUrls: output.avatarUrls,
|
||||
errorMessage: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
get viewModel(): RequestAvatarGenerationOutput {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
return this.result;
|
||||
getResponseModel(): RequestAvatarGenerationResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
getViewModel(): RequestAvatarGenerationOutput {
|
||||
return this.viewModel;
|
||||
get responseModel(): RequestAvatarGenerationResponseModel {
|
||||
if (!this.model) throw new Error('Presenter not presented');
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,45 @@
|
||||
import type { IUpdateAvatarPresenter, UpdateAvatarResult } from '@core/media/application/presenters/IUpdateAvatarPresenter';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
UpdateAvatarResult,
|
||||
UpdateAvatarErrorCode,
|
||||
} from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
import type { UpdateAvatarOutputDTO } from '../dtos/UpdateAvatarOutputDTO';
|
||||
|
||||
type UpdateAvatarOutput = UpdateAvatarOutputDTO;
|
||||
|
||||
export class UpdateAvatarPresenter implements IUpdateAvatarPresenter {
|
||||
private result: UpdateAvatarResult | null = null;
|
||||
|
||||
present(result: UpdateAvatarResult) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
get viewModel(): UpdateAvatarOutput {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
type UpdateAvatarResponseModel = UpdateAvatarOutputDTO;
|
||||
|
||||
return {
|
||||
success: this.result.success,
|
||||
error: this.result.errorMessage,
|
||||
export type UpdateAvatarApplicationError = ApplicationErrorCode<
|
||||
UpdateAvatarErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
export class UpdateAvatarPresenter {
|
||||
private model: UpdateAvatarResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: Result<UpdateAvatarResult, UpdateAvatarApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
throw new Error(error.details?.message ?? 'Failed to update avatar');
|
||||
}
|
||||
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
success: true,
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
getResponseModel(): UpdateAvatarResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): UpdateAvatarResponseModel {
|
||||
if (!this.model) throw new Error('Presenter not presented');
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,52 @@
|
||||
import type { IUploadMediaPresenter, UploadMediaResult } from '@core/media/application/presenters/IUploadMediaPresenter';
|
||||
import type { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type {
|
||||
UploadMediaResult,
|
||||
UploadMediaErrorCode,
|
||||
} from '@core/media/application/use-cases/UploadMediaUseCase';
|
||||
import type { UploadMediaOutputDTO } from '../dtos/UploadMediaOutputDTO';
|
||||
|
||||
type UploadMediaOutput = UploadMediaOutputDTO;
|
||||
type UploadMediaResponseModel = UploadMediaOutputDTO;
|
||||
|
||||
export class UploadMediaPresenter implements IUploadMediaPresenter {
|
||||
private result: UploadMediaResult | null = null;
|
||||
export type UploadMediaApplicationError = ApplicationErrorCode<
|
||||
UploadMediaErrorCode,
|
||||
{ message: string }
|
||||
>;
|
||||
|
||||
present(result: UploadMediaResult) {
|
||||
this.result = result;
|
||||
export class UploadMediaPresenter {
|
||||
private model: UploadMediaResponseModel | null = null;
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
get viewModel(): UploadMediaOutput {
|
||||
if (!this.result) throw new Error('Presenter not presented');
|
||||
present(result: Result<UploadMediaResult, UploadMediaApplicationError>): void {
|
||||
if (result.isErr()) {
|
||||
const error = result.unwrapErr();
|
||||
|
||||
if (this.result.success) {
|
||||
return {
|
||||
success: true,
|
||||
mediaId: this.result.mediaId,
|
||||
url: this.result.url,
|
||||
this.model = {
|
||||
success: false,
|
||||
error: error.details?.message ?? 'Upload failed',
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: this.result.errorMessage || 'Upload failed',
|
||||
const output = result.unwrap();
|
||||
|
||||
this.model = {
|
||||
success: true,
|
||||
mediaId: output.mediaId,
|
||||
url: output.url,
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getResponseModel(): UploadMediaResponseModel | null {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
get responseModel(): UploadMediaResponseModel {
|
||||
if (!this.model) throw new Error('Presenter not presented');
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user