use std::sync::Arc; use thiserror::Error; use async_trait::async_trait; use crate::domain::repositories::file_repository::FileRepositoryError; use crate::application::dtos::file_dto::FileDto; use crate::application::ports::inbound::FileUseCase; use crate::application::ports::outbound::FileStoragePort; use crate::common::errors::DomainError; use futures::Stream; use bytes::Bytes; /// Errores específicos del servicio de archivos #[derive(Debug, Error)] pub enum FileServiceError { #[error("Archivo no encontrado: {0}")] NotFound(String), #[error("Archivo ya existe: {0}")] Conflict(String), #[error("Error de acceso al archivo: {0}")] AccessError(String), #[error("Ruta de archivo inválida: {0}")] InvalidPath(String), #[error("Error interno: {0}")] InternalError(String), } impl From for FileServiceError { fn from(err: FileRepositoryError) -> Self { match err { FileRepositoryError::NotFound(id) => FileServiceError::NotFound(id), FileRepositoryError::AlreadyExists(path) => FileServiceError::Conflict(path), FileRepositoryError::InvalidPath(path) => FileServiceError::InvalidPath(path), FileRepositoryError::IoError(e) => FileServiceError::AccessError(e.to_string()), FileRepositoryError::Timeout(msg) => FileServiceError::AccessError(format!("Operación expiró: {}", msg)), _ => FileServiceError::InternalError(err.to_string()), } } } impl From for FileServiceError { fn from(err: DomainError) -> Self { match err.kind { crate::common::errors::ErrorKind::NotFound => FileServiceError::NotFound(err.to_string()), crate::common::errors::ErrorKind::AlreadyExists => FileServiceError::Conflict(err.to_string()), crate::common::errors::ErrorKind::InvalidInput => FileServiceError::InvalidPath(err.to_string()), crate::common::errors::ErrorKind::AccessDenied => FileServiceError::AccessError(err.to_string()), _ => FileServiceError::InternalError(err.to_string()), } } } impl From for DomainError { fn from(err: FileServiceError) -> Self { match err { FileServiceError::NotFound(id) => DomainError::not_found("File", id), FileServiceError::Conflict(path) => DomainError::already_exists("File", path), FileServiceError::InvalidPath(path) => DomainError::validation_error("File", format!("Invalid path: {}", path)), FileServiceError::AccessError(msg) => DomainError::access_denied("File", msg), FileServiceError::InternalError(msg) => DomainError::internal_error("File", msg), } } } pub type FileServiceResult = Result; /// Service for file operations pub struct FileService { file_repository: Arc, } impl FileService { /// Creates a new file service pub fn new(file_repository: Arc) -> Self { Self { file_repository } } /// Uploads a new file from bytes pub async fn upload_file_from_bytes( &self, name: String, folder_id: Option, content_type: String, content: Vec, ) -> FileServiceResult { let file = self.file_repository.save_file(name, folder_id, content_type, content).await .map_err(FileServiceError::from)?; Ok(FileDto::from(file)) } /// Gets a file by ID pub async fn get_file(&self, id: &str) -> FileServiceResult { let file = self.file_repository.get_file(id).await .map_err(FileServiceError::from)?; Ok(FileDto::from(file)) } /// Lists files in a folder pub async fn list_files(&self, folder_id: Option<&str>) -> FileServiceResult> { let files = self.file_repository.list_files(folder_id).await .map_err(FileServiceError::from)?; Ok(files.into_iter().map(FileDto::from).collect()) } /// Deletes a file pub async fn delete_file(&self, id: &str) -> FileServiceResult<()> { self.file_repository.delete_file(id).await .map_err(FileServiceError::from) } /// Gets file content as bytes - use for small files only pub async fn get_file_content(&self, id: &str) -> FileServiceResult> { self.file_repository.get_file_content(id).await .map_err(FileServiceError::from) } /// Gets file content as stream - better for large files pub async fn get_file_stream(&self, id: &str) -> FileServiceResult> + Send>> { self.file_repository.get_file_stream(id).await .map_err(FileServiceError::from) } /// Moves a file to a new folder using filesystem operations directly pub async fn move_file(&self, file_id: &str, folder_id: Option) -> FileServiceResult { tracing::info!("Moviendo archivo con ID: {} a carpeta: {:?}", file_id, folder_id); // Usar la implementación eficiente del repositorio que utiliza rename let moved_file = self.file_repository.move_file(file_id, folder_id).await .map_err(|e| { tracing::error!("Error al mover archivo (ID: {}): {}", file_id, e); FileServiceError::from(e) })?; tracing::info!("Archivo movido exitosamente: {} (ID: {}) a carpeta: {:?}", moved_file.name(), moved_file.id(), moved_file.folder_id()); Ok(FileDto::from(moved_file)) } } #[async_trait] impl FileUseCase for FileService { async fn upload_file( &self, name: String, folder_id: Option, content_type: String, content: Vec, ) -> Result { FileService::upload_file_from_bytes(self, name, folder_id, content_type, content).await .map_err(DomainError::from) } async fn get_file(&self, id: &str) -> Result { FileService::get_file(self, id).await .map_err(DomainError::from) } async fn list_files(&self, folder_id: Option<&str>) -> Result, DomainError> { FileService::list_files(self, folder_id).await .map_err(DomainError::from) } async fn delete_file(&self, id: &str) -> Result<(), DomainError> { FileService::delete_file(self, id).await .map_err(DomainError::from) } async fn get_file_content(&self, id: &str) -> Result, DomainError> { FileService::get_file_content(self, id).await .map_err(DomainError::from) } async fn get_file_stream(&self, id: &str) -> Result> + Send>, DomainError> { FileService::get_file_stream(self, id).await .map_err(DomainError::from) } async fn move_file(&self, file_id: &str, folder_id: Option) -> Result { FileService::move_file(self, file_id, folder_id).await .map_err(DomainError::from) } }