/** * Use Case: UploadMediaUseCase * * Handles the business logic for uploading media files. */ import type { IMediaRepository } from '../../domain/repositories/IMediaRepository'; import type { MediaStoragePort } from '../ports/MediaStoragePort'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { Media } from '../../domain/entities/Media'; import { v4 as uuidv4 } from 'uuid'; // Define Multer file type locally since @types/multer is not available export interface MulterFile { fieldname: string; originalname: string; encoding: string; mimetype: string; size: number; buffer: Buffer; stream: NodeJS.ReadableStream; destination: string; filename: string; path: string; } export interface UploadMediaInput { file: MulterFile; uploadedBy: string; metadata?: Record; } export interface UploadMediaResult { mediaId: string; url: string | undefined; } export type UploadMediaErrorCode = | 'UPLOAD_FAILED' | 'REPOSITORY_ERROR'; export class UploadMediaUseCase { constructor( private readonly mediaRepo: IMediaRepository, private readonly mediaStorage: MediaStoragePort, private readonly output: UseCaseOutputPort, private readonly logger: Logger, ) {} async execute( input: UploadMediaInput, ): Promise>> { this.logger.info('[UploadMediaUseCase] Starting media upload', { filename: input.file.originalname, size: input.file.size, uploadedBy: input.uploadedBy, }); try { // Upload file to storage service const uploadOptions: { filename: string; mimeType: string; metadata?: Record } = { filename: input.file.originalname, mimeType: input.file.mimetype, }; if (input.metadata !== undefined) { uploadOptions.metadata = input.metadata; } const uploadResult = await this.mediaStorage.uploadMedia(input.file.buffer, uploadOptions); if (!uploadResult.success || !uploadResult.url) { return Result.err>({ code: 'UPLOAD_FAILED', details: { message: uploadResult.errorMessage ?? 'Failed to upload media', }, }); } // Determine media type const mediaType: 'image' | 'video' | 'document' = input.file.mimetype.startsWith('image/') ? 'image' : input.file.mimetype.startsWith('video/') ? 'video' : 'document'; // Create media entity const mediaId = uuidv4(); const mediaProps: Parameters[0] = { id: mediaId, filename: uploadResult.filename || input.file.originalname, originalName: input.file.originalname, mimeType: input.file.mimetype, size: input.file.size, url: uploadResult.url, type: mediaType, uploadedBy: input.uploadedBy, }; if (input.metadata !== undefined) { mediaProps.metadata = input.metadata; } const media = Media.create(mediaProps); // Save to repository await this.mediaRepo.save(media); const result: UploadMediaResult = { mediaId, url: uploadResult.url, }; this.output.present(result); this.logger.info('[UploadMediaUseCase] Media uploaded successfully', { mediaId, url: uploadResult.url, }); return Result.ok(undefined); } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); this.logger.error('[UploadMediaUseCase] Error uploading media', err, { filename: input.file.originalname, }); return Result.err>({ code: 'REPOSITORY_ERROR', details: { message: err.message }, }); } } }