refactor: remove serde from domain entities for Clean Architecture compliance
- Remove Serialize/Deserialize from File, Folder, Session, User, Contact entities - Create contact_persistence_dto.rs for JSONB persistence in infrastructure layer - Update contact_pg_repository to use persistence DTOs - Fix dependency on zip crate (downgrade from 7.2.0 to 2.1.0) - Fix unused variable warnings in main.rs - Move PathService import from domain to infrastructure - Add missing fields to CoreServices and RepositoryServices - Create proper service initialization in main.rs Clean Architecture improvements: - Domain layer no longer depends on serde framework - Persistence concerns isolated to infrastructure layer - TokenClaims in auth_service.rs is only exception (required for JWT)
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
//! Adaptadores para convertir entre interfaces de dominio y aplicación
|
||||
//!
|
||||
//! Este módulo contiene adaptadores que permiten usar repositorios que implementan
|
||||
//! `FileStoragePort` y `FolderStoragePort` donde se espera `FileRepository` y `FolderRepository`.
|
||||
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::application::ports::outbound::{FileStoragePort, FolderStoragePort};
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::domain::entities::folder::Folder;
|
||||
use crate::domain::repositories::file_repository::{FileRepository, FileRepositoryResult, FileRepositoryError};
|
||||
use crate::domain::repositories::folder_repository::{FolderRepository, FolderRepositoryResult, FolderRepositoryError};
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
|
||||
/// Adaptador que convierte FileStoragePort a FileRepository
|
||||
pub struct DomainFileRepoAdapter {
|
||||
repo: Arc<dyn FileStoragePort>,
|
||||
}
|
||||
|
||||
impl DomainFileRepoAdapter {
|
||||
pub fn new(repo: Arc<dyn FileStoragePort>) -> Self {
|
||||
Self { repo }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileRepository for DomainFileRepoAdapter {
|
||||
async fn save_file_from_bytes(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> FileRepositoryResult<File> {
|
||||
self.repo.save_file(name, folder_id, content_type, content)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn save_file_with_id(
|
||||
&self,
|
||||
_id: String,
|
||||
_name: String,
|
||||
_folder_id: Option<String>,
|
||||
_content_type: String,
|
||||
_content: Vec<u8>,
|
||||
) -> FileRepositoryResult<File> {
|
||||
Err(FileRepositoryError::Other("Not implemented".to_string()))
|
||||
}
|
||||
|
||||
async fn get_file_by_id(&self, id: &str) -> FileRepositoryResult<File> {
|
||||
self.repo.get_file(id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn list_files(&self, folder_id: Option<&str>) -> FileRepositoryResult<Vec<File>> {
|
||||
self.repo.list_files(folder_id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn delete_file(&self, id: &str) -> FileRepositoryResult<()> {
|
||||
self.repo.delete_file(id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn delete_file_entry(&self, id: &str) -> FileRepositoryResult<()> {
|
||||
self.delete_file(id).await
|
||||
}
|
||||
|
||||
async fn get_file_content(&self, id: &str) -> FileRepositoryResult<Vec<u8>> {
|
||||
self.repo.get_file_content(id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn get_file_stream(&self, id: &str) -> FileRepositoryResult<Box<dyn futures::Stream<Item = Result<bytes::Bytes, std::io::Error>> + Send>> {
|
||||
self.repo.get_file_stream(id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn move_file(&self, id: &str, target_folder_id: Option<String>) -> FileRepositoryResult<File> {
|
||||
self.repo.move_file(id, target_folder_id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn get_file_path(&self, id: &str) -> FileRepositoryResult<StoragePath> {
|
||||
self.repo.get_file_path(id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn move_to_trash(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||
self.repo.delete_file(file_id)
|
||||
.await
|
||||
.map_err(|e| FileRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn restore_from_trash(&self, file_id: &str, original_path: &str) -> FileRepositoryResult<()> {
|
||||
tracing::info!("Restoring file from trash: {} to {}", file_id, original_path);
|
||||
|
||||
match self.repo.get_file(file_id).await {
|
||||
Ok(_) => {
|
||||
let path_components: Vec<&str> = original_path.split('/').collect();
|
||||
let parent_folder: Option<String> = if path_components.len() > 1 {
|
||||
tracing::info!("Attempting to restore to parent folder from path: {}", original_path);
|
||||
None
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match self.repo.move_file(file_id, parent_folder).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully restored file from trash: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to restore file from trash: {}", e);
|
||||
Err(FileRepositoryError::Other(format!("Failed to restore file: {}", e)))
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("File not found in trash: {}", e);
|
||||
Err(FileRepositoryError::NotFound(file_id.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_file_permanently(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||
tracing::info!("Permanently deleting file: {}", file_id);
|
||||
|
||||
match self.repo.delete_file(file_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully deleted file permanently: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to permanently delete file: {}", e);
|
||||
Err(FileRepositoryError::Other(format!("Failed to delete file permanently: {}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_file_content(&self, file_id: &str, content: Vec<u8>) -> FileRepositoryResult<()> {
|
||||
tracing::info!("Updating content for file: {}", file_id);
|
||||
|
||||
self.repo.update_file_content(file_id, content)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to update file content: {}", e);
|
||||
FileRepositoryError::Other(format!("Failed to update file content: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Adaptador que convierte FolderStoragePort a FolderRepository
|
||||
pub struct DomainFolderRepoAdapter {
|
||||
repo: Arc<dyn FolderStoragePort>,
|
||||
}
|
||||
|
||||
impl DomainFolderRepoAdapter {
|
||||
pub fn new(repo: Arc<dyn FolderStoragePort>) -> Self {
|
||||
Self { repo }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FolderRepository for DomainFolderRepoAdapter {
|
||||
async fn create_folder(&self, name: String, parent_id: Option<String>) -> FolderRepositoryResult<Folder> {
|
||||
self.repo.create_folder(name, parent_id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn get_folder_by_id(&self, id: &str) -> FolderRepositoryResult<Folder> {
|
||||
self.repo.get_folder(id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn get_folder_by_storage_path(&self, storage_path: &StoragePath) -> FolderRepositoryResult<Folder> {
|
||||
self.repo.get_folder_by_path(storage_path)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn list_folders(&self, parent_id: Option<&str>) -> FolderRepositoryResult<Vec<Folder>> {
|
||||
self.repo.list_folders(parent_id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn list_folders_paginated(
|
||||
&self,
|
||||
parent_id: Option<&str>,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
include_total: bool
|
||||
) -> FolderRepositoryResult<(Vec<Folder>, Option<usize>)> {
|
||||
self.repo.list_folders_paginated(parent_id, offset, limit, include_total)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn rename_folder(&self, id: &str, new_name: String) -> FolderRepositoryResult<Folder> {
|
||||
self.repo.rename_folder(id, new_name)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn move_folder(&self, id: &str, new_parent_id: Option<&str>) -> FolderRepositoryResult<Folder> {
|
||||
self.repo.move_folder(id, new_parent_id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn delete_folder(&self, id: &str) -> FolderRepositoryResult<()> {
|
||||
self.repo.delete_folder(id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn folder_exists_at_storage_path(&self, storage_path: &StoragePath) -> FolderRepositoryResult<bool> {
|
||||
self.repo.folder_exists(storage_path)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn get_folder_storage_path(&self, id: &str) -> FolderRepositoryResult<StoragePath> {
|
||||
self.repo.get_folder_path(id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn folder_exists(&self, _path: &std::path::PathBuf) -> FolderRepositoryResult<bool> {
|
||||
Err(FolderRepositoryError::Other("Not implemented".to_string()))
|
||||
}
|
||||
|
||||
async fn get_folder_by_path(&self, _path: &std::path::PathBuf) -> FolderRepositoryResult<Folder> {
|
||||
Err(FolderRepositoryError::Other("Not implemented".to_string()))
|
||||
}
|
||||
|
||||
async fn move_to_trash(&self, folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
self.repo.delete_folder(folder_id)
|
||||
.await
|
||||
.map_err(|e| FolderRepositoryError::Other(format!("{}", e)))
|
||||
}
|
||||
|
||||
async fn restore_from_trash(&self, _folder_id: &str, _original_path: &str) -> FolderRepositoryResult<()> {
|
||||
Err(FolderRepositoryError::Other(
|
||||
"Restore from trash should be handled by TrashService, not through this adapter".to_string()))
|
||||
}
|
||||
|
||||
async fn delete_folder_permanently(&self, folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
self.delete_folder(folder_id).await
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::domain::services::auth_service::AuthService;
|
||||
use crate::application::ports::auth_ports::TokenServicePort;
|
||||
use crate::application::services::auth_application_service::AuthApplicationService;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::infrastructure::repositories::{UserPgRepository, SessionPgRepository};
|
||||
use crate::infrastructure::services::password_hasher::Argon2PasswordHasher;
|
||||
use crate::infrastructure::services::jwt_service::JwtTokenService;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::common::di::AuthServices;
|
||||
|
||||
@@ -14,13 +16,16 @@ pub async fn create_auth_services(
|
||||
pool: Arc<PgPool>,
|
||||
folder_service: Option<Arc<FolderService>>
|
||||
) -> Result<AuthServices> {
|
||||
// Crear servicio de dominio de autenticación
|
||||
let auth_service = Arc::new(AuthService::new(
|
||||
// Crear servicio de tokens JWT (implementación de TokenServicePort)
|
||||
let token_service: Arc<dyn TokenServicePort> = Arc::new(JwtTokenService::new(
|
||||
config.auth.jwt_secret.clone(),
|
||||
config.auth.access_token_expiry_secs,
|
||||
config.auth.refresh_token_expiry_secs,
|
||||
));
|
||||
|
||||
// Crear servicio de hashing de contraseñas
|
||||
let password_hasher = Arc::new(Argon2PasswordHasher::new());
|
||||
|
||||
// Crear repositorios PostgreSQL
|
||||
let user_repository = Arc::new(UserPgRepository::new(pool.clone()));
|
||||
let session_repository = Arc::new(SessionPgRepository::new(pool.clone()));
|
||||
@@ -29,7 +34,8 @@ pub async fn create_auth_services(
|
||||
let mut auth_app_service = AuthApplicationService::new(
|
||||
user_repository,
|
||||
session_repository,
|
||||
auth_service.clone(),
|
||||
password_hasher,
|
||||
token_service.clone(),
|
||||
);
|
||||
|
||||
// Configurar servicio de carpetas si está disponible
|
||||
@@ -41,7 +47,7 @@ pub async fn create_auth_services(
|
||||
let auth_application_service = Arc::new(auth_app_service);
|
||||
|
||||
Ok(AuthServices {
|
||||
auth_service,
|
||||
token_service,
|
||||
auth_application_service,
|
||||
})
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::RwLock;
|
||||
use crate::common::errors::{DomainError, ErrorKind};
|
||||
|
||||
/// Entrada de caché con tiempo de expiración
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct CacheEntry<V> {
|
||||
value: V,
|
||||
expiry: Instant,
|
||||
}
|
||||
|
||||
impl<V> CacheEntry<V> {
|
||||
/// Crea una nueva entrada en la caché
|
||||
#[allow(dead_code)]
|
||||
fn new(value: V, ttl: Duration) -> Self {
|
||||
Self {
|
||||
value,
|
||||
expiry: Instant::now() + ttl,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifica si la entrada ha expirado
|
||||
#[allow(dead_code)]
|
||||
fn is_expired(&self) -> bool {
|
||||
Instant::now() > self.expiry
|
||||
}
|
||||
}
|
||||
|
||||
/// Servicio genérico de caché con TTL
|
||||
#[allow(dead_code)]
|
||||
pub struct CacheService<K, V> {
|
||||
cache: Arc<RwLock<HashMap<K, CacheEntry<V>>>>,
|
||||
ttl: Duration,
|
||||
max_entries: usize,
|
||||
}
|
||||
|
||||
impl<K, V> CacheService<K, V>
|
||||
where
|
||||
K: Hash + Eq + Clone + Send + Sync + 'static + std::fmt::Debug,
|
||||
V: Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Crea un nuevo servicio de caché
|
||||
#[allow(dead_code)]
|
||||
pub fn new(ttl: Duration, max_entries: usize) -> Self {
|
||||
Self {
|
||||
cache: Arc::new(RwLock::new(HashMap::new())),
|
||||
ttl,
|
||||
max_entries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene un valor de la caché o lo inserta si no existe
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_or_insert<F, E>(&self, key: K, loader: F) -> Result<V, DomainError>
|
||||
where
|
||||
F: FnOnce() -> Result<V, E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
// Intentar leer de la caché primero
|
||||
{
|
||||
let cache = self.cache.read().await;
|
||||
if let Some(entry) = cache.get(&key) {
|
||||
if !entry.is_expired() {
|
||||
tracing::debug!("Cache hit for key: {:?}", key);
|
||||
return Ok(entry.value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss o entrada expirada, obtener valor y actualizar
|
||||
let value = loader().map_err(|e| {
|
||||
DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Cache",
|
||||
format!("Failed to load value for cache: {}", e),
|
||||
)
|
||||
.with_source(e)
|
||||
})?;
|
||||
|
||||
// Insertar en la caché
|
||||
{
|
||||
let mut cache = self.cache.write().await;
|
||||
|
||||
// Si alcanzamos el límite, eliminar una entrada aleatoria
|
||||
if cache.len() >= self.max_entries {
|
||||
if let Some(expired_key) = cache
|
||||
.iter()
|
||||
.find(|(_, v)| v.is_expired())
|
||||
.map(|(k, _)| k.clone())
|
||||
{
|
||||
cache.remove(&expired_key);
|
||||
} else if let Some(random_key) = cache.keys().next().cloned() {
|
||||
cache.remove(&random_key);
|
||||
}
|
||||
}
|
||||
|
||||
cache.insert(key.clone(), CacheEntry::new(value.clone(), self.ttl));
|
||||
}
|
||||
|
||||
tracing::debug!("Cache miss for key: {:?}, value loaded and cached", key);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Invalida una entrada específica de la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn invalidate(&self, key: &K) {
|
||||
let mut cache = self.cache.write().await;
|
||||
cache.remove(key);
|
||||
tracing::debug!("Cache entry invalidated for key: {:?}", key);
|
||||
}
|
||||
|
||||
/// Invalida todas las entradas de la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn invalidate_all(&self) {
|
||||
let mut cache = self.cache.write().await;
|
||||
cache.clear();
|
||||
tracing::debug!("Cache fully invalidated");
|
||||
}
|
||||
|
||||
/// Obtiene el número de entradas en la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn len(&self) -> usize {
|
||||
self.cache.read().await.len()
|
||||
}
|
||||
|
||||
/// Limpia las entradas expiradas de la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn cleanup_expired(&self) -> usize {
|
||||
let mut cache = self.cache.write().await;
|
||||
let initial_len = cache.len();
|
||||
|
||||
cache.retain(|_, v| !v.is_expired());
|
||||
|
||||
let removed = initial_len - cache.len();
|
||||
if removed > 0 {
|
||||
tracing::debug!("Removed {} expired cache entries", removed);
|
||||
}
|
||||
|
||||
removed
|
||||
}
|
||||
}
|
||||
|
||||
/// Caché específica para metadatos de archivos
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct FileMetadata {
|
||||
pub size: u64,
|
||||
pub created_at: u64,
|
||||
pub modified_at: u64,
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
/// Gestor de caché para operaciones comunes de almacenamiento
|
||||
#[allow(dead_code)]
|
||||
pub struct CacheManager {
|
||||
/// Caché para metadatos de archivos/carpetas
|
||||
metadata_cache: CacheService<std::path::PathBuf, FileMetadata>,
|
||||
/// Caché para verificación de existencia de archivos
|
||||
existence_cache: CacheService<std::path::PathBuf, bool>,
|
||||
}
|
||||
|
||||
impl CacheManager {
|
||||
/// Crea un nuevo gestor de caché
|
||||
#[allow(dead_code)]
|
||||
pub fn new(metadata_ttl: Duration, existence_ttl: Duration) -> Self {
|
||||
Self {
|
||||
metadata_cache: CacheService::new(metadata_ttl, 10000), // Caché para 10,000 elementos
|
||||
existence_cache: CacheService::new(existence_ttl, 20000), // Caché para 20,000 elementos
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene o carga los metadatos de un archivo/carpeta
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_metadata<F>(&self, path: std::path::PathBuf, loader: F) -> Result<FileMetadata, DomainError>
|
||||
where
|
||||
F: FnOnce() -> Result<FileMetadata, std::io::Error>,
|
||||
{
|
||||
self.metadata_cache.get_or_insert(path, loader).await
|
||||
}
|
||||
|
||||
/// Verifica o determina si un archivo/carpeta existe
|
||||
#[allow(dead_code)]
|
||||
pub async fn check_exists<F>(&self, path: std::path::PathBuf, checker: F) -> Result<bool, DomainError>
|
||||
where
|
||||
F: FnOnce() -> Result<bool, std::io::Error>,
|
||||
{
|
||||
self.existence_cache.get_or_insert(path, checker).await
|
||||
}
|
||||
|
||||
/// Invalida la caché para una ruta específica
|
||||
#[allow(dead_code)]
|
||||
pub async fn invalidate_path(&self, path: &std::path::Path) {
|
||||
self.metadata_cache.invalidate(&path.to_path_buf()).await;
|
||||
self.existence_cache.invalidate(&path.to_path_buf()).await;
|
||||
}
|
||||
|
||||
/// Limpia todas las entradas expiradas
|
||||
#[allow(dead_code)]
|
||||
pub async fn cleanup(&self) -> (usize, usize) {
|
||||
let metadata_cleaned = self.metadata_cache.cleanup_expired().await;
|
||||
let existence_cleaned = self.existence_cache.cleanup_expired().await;
|
||||
(metadata_cleaned, existence_cleaned)
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ pub struct TimeoutConfig {
|
||||
/// Timeout para adquisición de locks (ms)
|
||||
pub lock_acquisition_ms: u64,
|
||||
/// Timeout para operaciones de red (ms)
|
||||
#[allow(dead_code)]
|
||||
pub network_operation_ms: u64,
|
||||
}
|
||||
|
||||
@@ -80,7 +79,6 @@ impl TimeoutConfig {
|
||||
}
|
||||
|
||||
/// Obtiene un Duration para operaciones de red
|
||||
#[allow(dead_code)]
|
||||
pub fn network_timeout(&self) -> Duration {
|
||||
Duration::from_millis(self.network_operation_ms)
|
||||
}
|
||||
@@ -92,7 +90,6 @@ pub struct ResourceConfig {
|
||||
/// Umbral en MB para considerar un archivo como grande
|
||||
pub large_file_threshold_mb: u64,
|
||||
/// Umbral de entradas para considerar un directorio como grande
|
||||
#[allow(dead_code)]
|
||||
pub large_dir_threshold_entries: usize,
|
||||
/// Tamaño de chunk para procesamiento de archivos grandes (bytes)
|
||||
pub chunk_size_bytes: usize,
|
||||
@@ -133,7 +130,6 @@ impl ResourceConfig {
|
||||
}
|
||||
|
||||
/// Determina si un directorio es considerado grande
|
||||
#[allow(dead_code)]
|
||||
pub fn is_large_directory(&self, entry_count: usize) -> bool {
|
||||
entry_count >= self.large_dir_threshold_entries
|
||||
}
|
||||
@@ -170,7 +166,6 @@ pub struct ConcurrencyConfig {
|
||||
/// Máximo de tareas de archivo concurrentes
|
||||
pub max_concurrent_files: usize,
|
||||
/// Máximo de tareas de directorio concurrentes
|
||||
#[allow(dead_code)]
|
||||
pub max_concurrent_dirs: usize,
|
||||
/// Máximo de operaciones de IO concurrentes
|
||||
pub max_concurrent_io: usize,
|
||||
@@ -451,7 +446,6 @@ impl AppConfig {
|
||||
}
|
||||
|
||||
/// Obtenemos una configuración global por defecto
|
||||
#[allow(dead_code)]
|
||||
pub fn default_config() -> AppConfig {
|
||||
AppConfig::default()
|
||||
}
|
||||
+229
-42
@@ -3,20 +3,29 @@ use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::domain::services::auth_service::AuthService;
|
||||
use crate::application::services::auth_application_service::AuthApplicationService;
|
||||
|
||||
use crate::domain::services::path_service::PathService;
|
||||
use crate::infrastructure::services::path_service::PathService;
|
||||
use crate::infrastructure::repositories::folder_fs_repository::FolderFsRepository;
|
||||
use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
||||
use crate::infrastructure::repositories::trash_fs_repository::TrashFsRepository;
|
||||
use crate::infrastructure::repositories::share_fs_repository::ShareFsRepository;
|
||||
use crate::infrastructure::repositories::parallel_file_processor::ParallelFileProcessor;
|
||||
use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService;
|
||||
use crate::infrastructure::services::id_mapping_service::IdMappingService;
|
||||
use crate::infrastructure::services::id_mapping_optimizer::IdMappingOptimizer;
|
||||
use crate::infrastructure::services::cache_manager::StorageCacheManager;
|
||||
use crate::infrastructure::services::file_metadata_cache::FileMetadataCache;
|
||||
use crate::infrastructure::services::buffer_pool::BufferPool;
|
||||
use crate::infrastructure::services::trash_cleanup_service::TrashCleanupService;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::file_service::FileService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::trash_service::TrashService;
|
||||
use crate::application::services::search_service::SearchService;
|
||||
use crate::application::services::share_service::ShareService;
|
||||
use crate::application::services::favorites_service::FavoritesService;
|
||||
use crate::application::services::recent_service::RecentService;
|
||||
use crate::application::ports::trash_ports::TrashUseCase;
|
||||
use crate::application::services::storage_mediator::{StorageMediator, FileSystemStorageMediator};
|
||||
use crate::application::ports::inbound::{FileUseCase, FolderUseCase, SearchUseCase};
|
||||
@@ -28,11 +37,14 @@ use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
||||
use crate::infrastructure::repositories::{FileMetadataManager, FilePathResolver, FileFsReadRepository, FileFsWriteRepository};
|
||||
use crate::application::services::{FileUploadService, FileRetrievalService, FileManagementService, AppFileUseCaseFactory};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::common::adapters::{DomainFileRepoAdapter, DomainFolderRepoAdapter};
|
||||
use crate::domain::services::i18n_service::I18nService;
|
||||
use crate::common::config::AppConfig;
|
||||
|
||||
/// Fábrica para los diferentes componentes de la aplicación
|
||||
#[allow(dead_code)]
|
||||
///
|
||||
/// Esta fábrica centraliza la creación de todos los servicios de la aplicación,
|
||||
/// garantizando el orden correcto de inicialización y resolviendo dependencias circulares.
|
||||
pub struct AppServiceFactory {
|
||||
storage_path: PathBuf,
|
||||
locales_path: PathBuf,
|
||||
@@ -41,7 +53,6 @@ pub struct AppServiceFactory {
|
||||
|
||||
impl AppServiceFactory {
|
||||
/// Crea una nueva fábrica de servicios
|
||||
#[allow(dead_code)]
|
||||
pub fn new(storage_path: PathBuf, locales_path: PathBuf) -> Self {
|
||||
Self {
|
||||
storage_path,
|
||||
@@ -51,7 +62,6 @@ impl AppServiceFactory {
|
||||
}
|
||||
|
||||
/// Crea una nueva fábrica de servicios con configuración personalizada
|
||||
#[allow(dead_code)]
|
||||
pub fn with_config(storage_path: PathBuf, locales_path: PathBuf, config: AppConfig) -> Self {
|
||||
Self {
|
||||
storage_path,
|
||||
@@ -60,17 +70,25 @@ impl AppServiceFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene la configuración
|
||||
pub fn config(&self) -> &AppConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Obtiene la ruta de almacenamiento
|
||||
pub fn storage_path(&self) -> &PathBuf {
|
||||
&self.storage_path
|
||||
}
|
||||
|
||||
/// Inicializa los servicios base del sistema
|
||||
#[allow(dead_code)]
|
||||
pub async fn create_core_services(&self) -> Result<CoreServices, DomainError> {
|
||||
// Path service
|
||||
let path_service = Arc::new(PathService::new(self.storage_path.clone()));
|
||||
|
||||
// Cache manager
|
||||
// TTL values in milliseconds and max entries for cache
|
||||
let file_ttl_ms = 60_000; // 1 minute for files
|
||||
let dir_ttl_ms = 120_000; // 2 minutes for directories
|
||||
let max_entries = 10_000; // Maximum cache entries
|
||||
let file_ttl_ms = self.config.cache.file_ttl_ms;
|
||||
let dir_ttl_ms = self.config.cache.directory_ttl_ms;
|
||||
let max_entries = self.config.cache.max_entries;
|
||||
let cache_manager = Arc::new(StorageCacheManager::new(file_ttl_ms, dir_ttl_ms, max_entries));
|
||||
|
||||
// Iniciar tarea de limpieza de caché en segundo plano
|
||||
@@ -79,22 +97,39 @@ impl AppServiceFactory {
|
||||
StorageCacheManager::start_cleanup_task(cache_manager_clone).await;
|
||||
});
|
||||
|
||||
// ID mapping service
|
||||
let id_mapping_path = self.storage_path.join("folder_ids.json");
|
||||
let id_mapping_service = Arc::new(
|
||||
IdMappingService::new(id_mapping_path).await?
|
||||
// ID mapping service para carpetas
|
||||
let folder_id_mapping_path = self.storage_path.join("folder_ids.json");
|
||||
let folder_id_mapping_service = Arc::new(
|
||||
IdMappingService::new(folder_id_mapping_path).await?
|
||||
);
|
||||
|
||||
// ID mapping service para archivos
|
||||
let file_id_mapping_path = self.storage_path.join("file_ids.json");
|
||||
let file_id_mapping_service = Arc::new(
|
||||
IdMappingService::new(file_id_mapping_path).await?
|
||||
);
|
||||
|
||||
// Optimizer con batch processing y caching
|
||||
let id_mapping_optimizer = Arc::new(
|
||||
IdMappingOptimizer::new(folder_id_mapping_service.clone())
|
||||
);
|
||||
|
||||
// Iniciar tarea de limpieza del optimizer
|
||||
IdMappingOptimizer::start_cleanup_task(id_mapping_optimizer.clone());
|
||||
|
||||
tracing::info!("Core services initialized: path service, cache manager, ID mapping");
|
||||
|
||||
Ok(CoreServices {
|
||||
path_service,
|
||||
cache_manager,
|
||||
id_mapping_service,
|
||||
id_mapping_service: folder_id_mapping_service,
|
||||
file_id_mapping_service,
|
||||
id_mapping_optimizer,
|
||||
config: self.config.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Inicializa los servicios de repositorio utilizando el patrón Builder mejorado
|
||||
#[allow(dead_code)]
|
||||
/// Inicializa los servicios de repositorio
|
||||
pub fn create_repository_services(&self, core: &CoreServices) -> RepositoryServices {
|
||||
// Storage mediator - con inicialización diferida para folder repository
|
||||
let folder_repository_holder = Arc::new(RwLock::new(None));
|
||||
@@ -102,7 +137,7 @@ impl AppServiceFactory {
|
||||
let storage_mediator = Arc::new(FileSystemStorageMediator::new_with_lazy_folder(
|
||||
folder_repository_holder.clone(),
|
||||
core.path_service.clone(),
|
||||
core.id_mapping_service.clone()
|
||||
core.id_mapping_optimizer.clone()
|
||||
));
|
||||
|
||||
// Folder repository
|
||||
@@ -113,7 +148,7 @@ impl AppServiceFactory {
|
||||
core.path_service.clone(),
|
||||
));
|
||||
|
||||
// Actualizar el holder para el mediador una vez que el repository está creado
|
||||
// Actualizar el holder para el mediador
|
||||
if let Ok(mut holder) = folder_repository_holder.write() {
|
||||
*holder = Some(folder_repository.clone());
|
||||
}
|
||||
@@ -123,6 +158,22 @@ impl AppServiceFactory {
|
||||
FileMetadataCache::default_with_config(core.config.clone())
|
||||
);
|
||||
|
||||
// Iniciar tarea de limpieza de metadata cache
|
||||
let cache_clone = metadata_cache.clone();
|
||||
tokio::spawn(async move {
|
||||
FileMetadataCache::start_cleanup_task(cache_clone).await;
|
||||
});
|
||||
|
||||
// Buffer pool para optimización de memoria
|
||||
let buffer_pool = BufferPool::new(256 * 1024, 50, 120); // 256KB buffers, 50 max, 2 min TTL
|
||||
BufferPool::start_cleaner(buffer_pool.clone());
|
||||
|
||||
// Parallel file processor
|
||||
let parallel_processor = Arc::new(ParallelFileProcessor::new_with_buffer_pool(
|
||||
core.config.clone(),
|
||||
buffer_pool.clone()
|
||||
));
|
||||
|
||||
// Componentes refactorizados
|
||||
let metadata_manager = Arc::new(FileMetadataManager::new(
|
||||
metadata_cache.clone(),
|
||||
@@ -141,7 +192,7 @@ impl AppServiceFactory {
|
||||
metadata_manager.clone(),
|
||||
path_resolver.clone(),
|
||||
core.config.clone(),
|
||||
None // processor will be added later if needed
|
||||
Some(parallel_processor.clone())
|
||||
));
|
||||
|
||||
let file_write_repository = Arc::new(FileFsWriteRepository::new(
|
||||
@@ -150,16 +201,17 @@ impl AppServiceFactory {
|
||||
path_resolver.clone(),
|
||||
storage_mediator.clone(),
|
||||
core.config.clone(),
|
||||
None // processor will be added later if needed
|
||||
Some(parallel_processor.clone())
|
||||
));
|
||||
|
||||
// Legacy file repository - mantenido por compatibilidad
|
||||
let file_repository = Arc::new(FileFsRepository::new(
|
||||
// File repository con procesamiento paralelo
|
||||
let file_repository = Arc::new(FileFsRepository::new_with_processor(
|
||||
self.storage_path.clone(),
|
||||
storage_mediator.clone(),
|
||||
core.id_mapping_service.clone(),
|
||||
core.file_id_mapping_service.clone(),
|
||||
core.path_service.clone(),
|
||||
metadata_cache,
|
||||
metadata_cache.clone(),
|
||||
parallel_processor
|
||||
));
|
||||
|
||||
// I18n repository
|
||||
@@ -177,6 +229,8 @@ impl AppServiceFactory {
|
||||
None
|
||||
};
|
||||
|
||||
tracing::info!("Repository services initialized with parallel processing and buffer pool");
|
||||
|
||||
RepositoryServices {
|
||||
folder_repository,
|
||||
file_repository,
|
||||
@@ -186,24 +240,23 @@ impl AppServiceFactory {
|
||||
storage_mediator,
|
||||
metadata_manager,
|
||||
path_resolver,
|
||||
metadata_cache,
|
||||
trash_repository,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inicializa los servicios de aplicación
|
||||
#[allow(dead_code)]
|
||||
pub fn create_application_services(&self, repos: &RepositoryServices) -> ApplicationServices {
|
||||
// Servicios principales
|
||||
let folder_service = Arc::new(FolderService::new(
|
||||
repos.folder_repository.clone()
|
||||
));
|
||||
|
||||
// Antiguo servicio único
|
||||
let file_service = Arc::new(FileService::new(
|
||||
repos.file_repository.clone()
|
||||
));
|
||||
|
||||
// Nuevos servicios refactorizados
|
||||
// Servicios refactorizados
|
||||
let file_upload_service = Arc::new(FileUploadService::new(
|
||||
repos.file_write_repository.clone()
|
||||
));
|
||||
@@ -225,13 +278,21 @@ impl AppServiceFactory {
|
||||
repos.i18n_repository.clone()
|
||||
));
|
||||
|
||||
// Servicio de papelera (deshabilitado temporalmente)
|
||||
let trash_service = None; // La función de papelera está deshabilitada por defecto
|
||||
// Search service con caché
|
||||
let search_service: Option<Arc<dyn SearchUseCase>> = Some(Arc::new(SearchService::new(
|
||||
repos.file_repository.clone(),
|
||||
repos.folder_repository.clone(),
|
||||
300, // Cache TTL in seconds (5 minutes)
|
||||
1000, // Maximum cache entries
|
||||
)));
|
||||
|
||||
// Servicio de búsqueda (deshabilitado por defecto)
|
||||
let search_service = None; // La función de búsqueda se activa según la configuración
|
||||
tracing::info!("Application services initialized");
|
||||
|
||||
ApplicationServices {
|
||||
// Tipos concretos para handlers que los necesitan
|
||||
folder_service_concrete: folder_service.clone(),
|
||||
file_service_concrete: file_service.clone(),
|
||||
// Traits para abstracción
|
||||
folder_service,
|
||||
file_service,
|
||||
file_upload_service,
|
||||
@@ -239,27 +300,132 @@ impl AppServiceFactory {
|
||||
file_management_service,
|
||||
file_use_case_factory,
|
||||
i18n_service,
|
||||
trash_service,
|
||||
trash_service: None, // Se configura después con create_trash_service
|
||||
search_service,
|
||||
share_service: None, // No share service by default
|
||||
favorites_service: None, // No favorites service by default
|
||||
recent_service: None // No recent service by default
|
||||
share_service: None, // Se configura después con create_share_service
|
||||
favorites_service: None, // Se configura después con create_favorites_service
|
||||
recent_service: None, // Se configura después con create_recent_service
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea el servicio de papelera
|
||||
pub async fn create_trash_service(
|
||||
&self,
|
||||
repos: &RepositoryServices,
|
||||
) -> Option<Arc<dyn TrashUseCase>> {
|
||||
if !self.config.features.enable_trash {
|
||||
tracing::info!("Trash service is disabled in configuration");
|
||||
return None;
|
||||
}
|
||||
|
||||
let trash_repo = repos.trash_repository.as_ref()?;
|
||||
|
||||
// Crear adaptadores
|
||||
let file_repo_adapter = Arc::new(DomainFileRepoAdapter::new(repos.file_repository.clone()));
|
||||
let folder_repo_adapter = Arc::new(DomainFolderRepoAdapter::new(repos.folder_repository.clone()));
|
||||
|
||||
let service = Arc::new(TrashService::new(
|
||||
trash_repo.clone(),
|
||||
file_repo_adapter,
|
||||
folder_repo_adapter,
|
||||
self.config.storage.trash_retention_days,
|
||||
));
|
||||
|
||||
// Inicializar servicio de limpieza
|
||||
let cleanup_service = TrashCleanupService::new(
|
||||
service.clone(),
|
||||
trash_repo.clone(),
|
||||
24, // Run cleanup every 24 hours
|
||||
);
|
||||
|
||||
cleanup_service.start_cleanup_job().await;
|
||||
tracing::info!("Trash service initialized with daily cleanup schedule");
|
||||
|
||||
Some(service as Arc<dyn TrashUseCase>)
|
||||
}
|
||||
|
||||
/// Crea el servicio de compartición
|
||||
pub fn create_share_service(
|
||||
&self,
|
||||
repos: &RepositoryServices,
|
||||
) -> Option<Arc<dyn crate::application::ports::share_ports::ShareUseCase>> {
|
||||
if !self.config.features.enable_file_sharing {
|
||||
tracing::info!("File sharing service is disabled in configuration");
|
||||
return None;
|
||||
}
|
||||
|
||||
let share_repository = Arc::new(ShareFsRepository::new(
|
||||
Arc::new(self.config.clone())
|
||||
));
|
||||
|
||||
let service = Arc::new(ShareService::new(
|
||||
Arc::new(self.config.clone()),
|
||||
share_repository,
|
||||
repos.file_repository.clone(),
|
||||
repos.folder_repository.clone()
|
||||
));
|
||||
|
||||
tracing::info!("File sharing service initialized");
|
||||
Some(service)
|
||||
}
|
||||
|
||||
/// Crea el servicio de favoritos (requiere base de datos)
|
||||
pub fn create_favorites_service(
|
||||
&self,
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> Arc<dyn FavoritesUseCase> {
|
||||
let service = Arc::new(FavoritesService::new(db_pool.clone()));
|
||||
tracing::info!("Favorites service initialized");
|
||||
service
|
||||
}
|
||||
|
||||
/// Crea el servicio de elementos recientes (requiere base de datos)
|
||||
pub fn create_recent_service(
|
||||
&self,
|
||||
db_pool: &Arc<PgPool>,
|
||||
) -> Arc<dyn RecentItemsUseCase> {
|
||||
let service = Arc::new(RecentService::new(
|
||||
db_pool.clone(),
|
||||
50 // Maximum recent items per user
|
||||
));
|
||||
tracing::info!("Recent items service initialized");
|
||||
service
|
||||
}
|
||||
|
||||
/// Precarga traducciones
|
||||
pub async fn preload_translations(&self, i18n_service: &I18nApplicationService) {
|
||||
use crate::domain::services::i18n_service::Locale;
|
||||
|
||||
if let Err(e) = i18n_service.load_translations(Locale::English).await {
|
||||
tracing::warn!("Failed to load English translations: {}", e);
|
||||
}
|
||||
if let Err(e) = i18n_service.load_translations(Locale::Spanish).await {
|
||||
tracing::warn!("Failed to load Spanish translations: {}", e);
|
||||
}
|
||||
tracing::info!("Translations preloaded");
|
||||
}
|
||||
|
||||
/// Precarga directorios en caché
|
||||
pub async fn preload_cache(&self, metadata_cache: &FileMetadataCache) {
|
||||
tracing::info!("Preloading common directories to warm up cache...");
|
||||
if let Ok(count) = metadata_cache.preload_directory(&self.storage_path, true, 1).await {
|
||||
tracing::info!("Preloaded {} directory entries into cache", count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contenedor para servicios base
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct CoreServices {
|
||||
pub path_service: Arc<PathService>,
|
||||
pub cache_manager: Arc<StorageCacheManager>,
|
||||
pub id_mapping_service: Arc<dyn crate::application::ports::outbound::IdMappingPort>,
|
||||
pub file_id_mapping_service: Arc<IdMappingService>,
|
||||
pub id_mapping_optimizer: Arc<IdMappingOptimizer>,
|
||||
pub config: AppConfig,
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de repositorio
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct RepositoryServices {
|
||||
pub folder_repository: Arc<dyn FolderStoragePort>,
|
||||
@@ -270,13 +436,17 @@ pub struct RepositoryServices {
|
||||
pub storage_mediator: Arc<dyn StorageMediator>,
|
||||
pub metadata_manager: Arc<FileMetadataManager>,
|
||||
pub path_resolver: Arc<FilePathResolver>,
|
||||
pub metadata_cache: Arc<FileMetadataCache>,
|
||||
pub trash_repository: Option<Arc<dyn crate::domain::repositories::trash_repository::TrashRepository>>,
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de aplicación
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct ApplicationServices {
|
||||
// Tipos concretos para compatibilidad con handlers existentes
|
||||
pub folder_service_concrete: Arc<FolderService>,
|
||||
pub file_service_concrete: Arc<FileService>,
|
||||
// Traits para abstracción
|
||||
pub folder_service: Arc<dyn FolderUseCase>,
|
||||
pub file_service: Arc<dyn FileUseCase>,
|
||||
pub file_upload_service: Arc<dyn FileUploadUseCase>,
|
||||
@@ -292,10 +462,9 @@ pub struct ApplicationServices {
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de autenticación
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct AuthServices {
|
||||
pub auth_service: Arc<AuthService>,
|
||||
pub token_service: Arc<dyn crate::application::ports::auth_ports::TokenServicePort>,
|
||||
pub auth_application_service: Arc<AuthApplicationService>,
|
||||
}
|
||||
|
||||
@@ -323,7 +492,7 @@ impl Default for AppState {
|
||||
|
||||
let config = crate::common::config::AppConfig::default();
|
||||
let path_service = Arc::new(
|
||||
crate::domain::services::path_service::PathService::new(
|
||||
crate::infrastructure::services::path_service::PathService::new(
|
||||
std::path::PathBuf::from("./storage")
|
||||
)
|
||||
);
|
||||
@@ -758,14 +927,23 @@ impl Default for AppState {
|
||||
let file_management_service = Arc::new(DummyFileManagementUseCase) as Arc<dyn crate::application::ports::file_ports::FileManagementUseCase>;
|
||||
let file_use_case_factory = Arc::new(DummyFileUseCaseFactory) as Arc<dyn crate::application::ports::file_ports::FileUseCaseFactory>;
|
||||
|
||||
// Create dummy ID mapping service for files
|
||||
let dummy_file_id_mapping = Arc::new(IdMappingService::dummy());
|
||||
let dummy_id_optimizer = Arc::new(IdMappingOptimizer::new(dummy_file_id_mapping.clone()));
|
||||
|
||||
// This creates the core services needed for basic functionality
|
||||
let core_services = CoreServices {
|
||||
path_service: path_service.clone(),
|
||||
cache_manager: Arc::new(crate::infrastructure::services::cache_manager::StorageCacheManager::default()),
|
||||
id_mapping_service: id_mapping_service.clone(),
|
||||
file_id_mapping_service: dummy_file_id_mapping,
|
||||
id_mapping_optimizer: dummy_id_optimizer,
|
||||
config: config.clone(),
|
||||
};
|
||||
|
||||
// Create dummy metadata cache
|
||||
let dummy_metadata_cache = Arc::new(FileMetadataCache::default_with_config(config.clone()));
|
||||
|
||||
// Create empty repository implementations
|
||||
let repository_services = RepositoryServices {
|
||||
folder_repository: Arc::new(DummyFolderStoragePort) as Arc<dyn crate::application::ports::outbound::FolderStoragePort>,
|
||||
@@ -780,6 +958,7 @@ impl Default for AppState {
|
||||
storage_mediator.clone(),
|
||||
id_mapping_service.clone()
|
||||
)),
|
||||
metadata_cache: dummy_metadata_cache,
|
||||
trash_repository: None, // No trash repository in minimal mode
|
||||
};
|
||||
|
||||
@@ -798,9 +977,17 @@ impl Default for AppState {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Create dummy concrete services for compatibility
|
||||
let dummy_folder_storage = Arc::new(DummyFolderStoragePort) as Arc<dyn crate::application::ports::outbound::FolderStoragePort>;
|
||||
let dummy_file_storage = Arc::new(DummyFileStoragePort) as Arc<dyn crate::application::ports::outbound::FileStoragePort>;
|
||||
let folder_service_concrete = Arc::new(FolderService::new(dummy_folder_storage));
|
||||
let file_service_concrete = Arc::new(FileService::new(dummy_file_storage));
|
||||
|
||||
// Create application services
|
||||
let application_services = ApplicationServices {
|
||||
folder_service_concrete: folder_service_concrete.clone(),
|
||||
file_service_concrete: file_service_concrete.clone(),
|
||||
folder_service,
|
||||
file_service,
|
||||
file_upload_service,
|
||||
|
||||
+13
-342
@@ -1,255 +1,22 @@
|
||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
use std::error::Error as StdError;
|
||||
use thiserror::Error;
|
||||
//! Errores de la aplicación
|
||||
//!
|
||||
//! Este módulo re-exporta los errores del dominio y define utilidades
|
||||
//! para conversión de errores de infraestructura.
|
||||
|
||||
/// Tipo Result común para la aplicación con DomainError como error estándar
|
||||
pub type Result<T> = std::result::Result<T, DomainError>;
|
||||
// Re-exportar errores del dominio para compatibilidad
|
||||
pub use crate::domain::errors::{DomainError, ErrorKind, Result};
|
||||
|
||||
/// Tipos de errores comunes en toda la aplicación
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
/// Entidad no encontrada
|
||||
NotFound,
|
||||
/// Entidad ya existe
|
||||
AlreadyExists,
|
||||
/// Entrada inválida o validación fallida
|
||||
InvalidInput,
|
||||
/// Error de acceso o permisos
|
||||
AccessDenied,
|
||||
/// Tiempo de espera agotado
|
||||
Timeout,
|
||||
/// Error interno del sistema
|
||||
InternalError,
|
||||
/// Funcionalidad no implementada
|
||||
NotImplemented,
|
||||
/// Operación no soportada
|
||||
UnsupportedOperation,
|
||||
/// Error de base de datos
|
||||
DatabaseError,
|
||||
}
|
||||
// Re-exportar AppError desde interfaces para compatibilidad hacia atrás
|
||||
// NOTA: El lugar canónico de AppError es ahora crate::interfaces::errors
|
||||
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
match self {
|
||||
ErrorKind::NotFound => write!(f, "Not Found"),
|
||||
ErrorKind::AlreadyExists => write!(f, "Already Exists"),
|
||||
ErrorKind::InvalidInput => write!(f, "Invalid Input"),
|
||||
ErrorKind::AccessDenied => write!(f, "Access Denied"),
|
||||
ErrorKind::Timeout => write!(f, "Timeout"),
|
||||
ErrorKind::InternalError => write!(f, "Internal Error"),
|
||||
ErrorKind::NotImplemented => write!(f, "Not Implemented"),
|
||||
ErrorKind::UnsupportedOperation => write!(f, "Unsupported Operation"),
|
||||
ErrorKind::DatabaseError => write!(f, "Database Error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error base de dominio que proporciona contexto detallado
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{kind}: {message}")]
|
||||
pub struct DomainError {
|
||||
/// Tipo de error
|
||||
pub kind: ErrorKind,
|
||||
/// Tipo de entidad afectada (ej: "File", "Folder")
|
||||
pub entity_type: &'static str,
|
||||
/// Identificador de la entidad si está disponible
|
||||
pub entity_id: Option<String>,
|
||||
/// Mensaje descriptivo del error
|
||||
pub message: String,
|
||||
/// Error fuente (opcional)
|
||||
#[source]
|
||||
pub source: Option<Box<dyn StdError + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl DomainError {
|
||||
/// Crea un nuevo error de dominio
|
||||
pub fn new<S: Into<String>>(
|
||||
kind: ErrorKind,
|
||||
entity_type: &'static str,
|
||||
message: S,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de entidad no encontrada
|
||||
pub fn not_found<S: Into<String>>(entity_type: &'static str, entity_id: S) -> Self {
|
||||
let id = entity_id.into();
|
||||
Self {
|
||||
kind: ErrorKind::NotFound,
|
||||
entity_type,
|
||||
entity_id: Some(id.clone()),
|
||||
message: format!("{} not found: {}", entity_type, id),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de entidad ya existente
|
||||
pub fn already_exists<S: Into<String>>(entity_type: &'static str, entity_id: S) -> Self {
|
||||
let id = entity_id.into();
|
||||
Self {
|
||||
kind: ErrorKind::AlreadyExists,
|
||||
entity_type,
|
||||
entity_id: Some(id.clone()),
|
||||
message: format!("{} already exists: {}", entity_type, id),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error para operaciones no soportadas
|
||||
pub fn operation_not_supported<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self::new(
|
||||
ErrorKind::UnsupportedOperation,
|
||||
entity_type,
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
/// Crea un error de tiempo agotado
|
||||
pub fn timeout<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Timeout,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error interno
|
||||
pub fn internal_error<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::InternalError,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de acceso denegado
|
||||
pub fn access_denied<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::AccessDenied,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for access_denied to maintain compatibility
|
||||
pub fn unauthorized<S: Into<String>>(message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::AccessDenied,
|
||||
entity_type: "Authorization",
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de base de datos
|
||||
pub fn database_error<S: Into<String>>(message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::DatabaseError,
|
||||
entity_type: "Database",
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de validación
|
||||
pub fn validation_error<S: Into<String>>(message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::InvalidInput,
|
||||
entity_type: "Validation",
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de funcionalidad no implementada
|
||||
pub fn not_implemented<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::NotImplemented,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Establece el ID de la entidad
|
||||
#[allow(dead_code)]
|
||||
pub fn with_id<S: Into<String>>(mut self, entity_id: S) -> Self {
|
||||
self.entity_id = Some(entity_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el error fuente
|
||||
pub fn with_source<E: StdError + Send + Sync + 'static>(mut self, source: E) -> Self {
|
||||
self.source = Some(Box::new(source));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait para añadir contexto a los errores
|
||||
pub trait ErrorContext<T, E> {
|
||||
fn with_context<C, F>(self, context: F) -> std::result::Result<T, DomainError>
|
||||
where
|
||||
C: Into<String>,
|
||||
F: FnOnce() -> C;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn with_error_kind(self, kind: ErrorKind, entity_type: &'static str) -> std::result::Result<T, DomainError>;
|
||||
}
|
||||
|
||||
impl<T, E: StdError + Send + Sync + 'static> ErrorContext<T, E> for std::result::Result<T, E> {
|
||||
fn with_context<C, F>(self, context: F) -> std::result::Result<T, DomainError>
|
||||
where
|
||||
C: Into<String>,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
DomainError {
|
||||
kind: ErrorKind::InternalError,
|
||||
entity_type: "Unknown",
|
||||
entity_id: None,
|
||||
message: context().into(),
|
||||
source: Some(Box::new(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn with_error_kind(self, kind: ErrorKind, entity_type: &'static str) -> std::result::Result<T, DomainError> {
|
||||
self.map_err(|e| {
|
||||
DomainError {
|
||||
kind,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: format!("{}", e),
|
||||
source: Some(Box::new(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro para convertir errores específicos a DomainError
|
||||
// Macro para convertir errores específicos de infraestructura a DomainError
|
||||
#[macro_export]
|
||||
macro_rules! impl_from_error {
|
||||
($error_type:ty, $entity_type:expr) => {
|
||||
impl From<$error_type> for DomainError {
|
||||
impl From<$error_type> for crate::domain::errors::DomainError {
|
||||
fn from(err: $error_type) -> Self {
|
||||
DomainError {
|
||||
kind: ErrorKind::InternalError,
|
||||
crate::domain::errors::DomainError {
|
||||
kind: crate::domain::errors::ErrorKind::InternalError,
|
||||
entity_type: $entity_type,
|
||||
entity_id: None,
|
||||
message: format!("{}", err),
|
||||
@@ -260,102 +27,6 @@ macro_rules! impl_from_error {
|
||||
};
|
||||
}
|
||||
|
||||
// Implementación para errores estándar comunes
|
||||
impl_from_error!(std::io::Error, "IO");
|
||||
// Implementaciones para errores de infraestructura (sqlx, serde_json)
|
||||
impl_from_error!(serde_json::Error, "Serialization");
|
||||
impl_from_error!(sqlx::Error, "Database");
|
||||
impl_from_error!(uuid::Error, "UUID");
|
||||
|
||||
// Error para capas HTTP/API
|
||||
#[derive(Debug)]
|
||||
pub struct AppError {
|
||||
pub status_code: axum::http::StatusCode,
|
||||
pub message: String,
|
||||
pub error_type: String,
|
||||
}
|
||||
|
||||
// Estructura de respuesta de error
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub status: String,
|
||||
pub message: String,
|
||||
pub error_type: String,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
pub fn new(status_code: axum::http::StatusCode, message: impl Into<String>, error_type: impl Into<String>) -> Self {
|
||||
Self {
|
||||
status_code,
|
||||
message: message.into(),
|
||||
error_type: error_type.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bad_request(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::BAD_REQUEST, message, "BadRequest")
|
||||
}
|
||||
|
||||
pub fn unauthorized(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::UNAUTHORIZED, message, "Unauthorized")
|
||||
}
|
||||
|
||||
pub fn forbidden(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::FORBIDDEN, message, "Forbidden")
|
||||
}
|
||||
|
||||
pub fn not_found(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::NOT_FOUND, message, "NotFound")
|
||||
}
|
||||
|
||||
pub fn internal_error(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::INTERNAL_SERVER_ERROR, message, "InternalError")
|
||||
}
|
||||
|
||||
pub fn method_not_allowed(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::METHOD_NOT_ALLOWED, message, "MethodNotAllowed")
|
||||
}
|
||||
|
||||
pub fn conflict(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::CONFLICT, message, "Conflict")
|
||||
}
|
||||
|
||||
pub fn unsupported_media_type(message: impl Into<String>) -> Self {
|
||||
Self::new(axum::http::StatusCode::UNSUPPORTED_MEDIA_TYPE, message, "UnsupportedMediaType")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomainError> for AppError {
|
||||
fn from(err: DomainError) -> Self {
|
||||
let status_code = match err.kind {
|
||||
ErrorKind::NotFound => axum::http::StatusCode::NOT_FOUND,
|
||||
ErrorKind::AlreadyExists => axum::http::StatusCode::CONFLICT,
|
||||
ErrorKind::InvalidInput => axum::http::StatusCode::BAD_REQUEST,
|
||||
ErrorKind::AccessDenied => axum::http::StatusCode::FORBIDDEN,
|
||||
ErrorKind::Timeout => axum::http::StatusCode::REQUEST_TIMEOUT,
|
||||
ErrorKind::InternalError => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ErrorKind::NotImplemented => axum::http::StatusCode::NOT_IMPLEMENTED,
|
||||
ErrorKind::UnsupportedOperation => axum::http::StatusCode::METHOD_NOT_ALLOWED,
|
||||
ErrorKind::DatabaseError => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
||||
Self {
|
||||
status_code,
|
||||
message: err.message,
|
||||
error_type: err.kind.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl axum::response::IntoResponse for AppError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let status = self.status_code;
|
||||
let error_response = ErrorResponse {
|
||||
status: status.to_string(),
|
||||
message: self.message,
|
||||
error_type: self.error_type,
|
||||
};
|
||||
|
||||
let body = axum::Json(error_response);
|
||||
(status, body).into_response()
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
pub mod errors;
|
||||
pub mod config;
|
||||
pub mod cache;
|
||||
pub mod di;
|
||||
pub mod db;
|
||||
pub mod auth_factory;
|
||||
pub mod auth_factory;
|
||||
pub mod adapters;
|
||||
Reference in New Issue
Block a user