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:
@@ -1,8 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use crate::domain::entities::user::{User, UserRole};
|
||||
use crate::domain::entities::session::Session;
|
||||
use crate::domain::services::auth_service::AuthService;
|
||||
use crate::application::ports::auth_ports::{UserStoragePort, SessionStoragePort};
|
||||
use crate::application::ports::auth_ports::{UserStoragePort, SessionStoragePort, PasswordHasherPort, TokenServicePort};
|
||||
use crate::application::dtos::user_dto::{UserDto, RegisterDto, LoginDto, AuthResponseDto, ChangePasswordDto, RefreshTokenDto};
|
||||
use crate::application::dtos::folder_dto::CreateFolderDto;
|
||||
use crate::application::ports::inbound::FolderUseCase;
|
||||
@@ -11,7 +10,8 @@ use crate::common::errors::{DomainError, ErrorKind};
|
||||
pub struct AuthApplicationService {
|
||||
user_storage: Arc<dyn UserStoragePort>,
|
||||
session_storage: Arc<dyn SessionStoragePort>,
|
||||
auth_service: Arc<AuthService>,
|
||||
password_hasher: Arc<dyn PasswordHasherPort>,
|
||||
token_service: Arc<dyn TokenServicePort>,
|
||||
folder_service: Option<Arc<dyn FolderUseCase>>,
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ impl AuthApplicationService {
|
||||
pub fn new(
|
||||
user_storage: Arc<dyn UserStoragePort>,
|
||||
session_storage: Arc<dyn SessionStoragePort>,
|
||||
auth_service: Arc<AuthService>,
|
||||
password_hasher: Arc<dyn PasswordHasherPort>,
|
||||
token_service: Arc<dyn TokenServicePort>,
|
||||
) -> Self {
|
||||
Self {
|
||||
user_storage,
|
||||
session_storage,
|
||||
auth_service,
|
||||
password_hasher,
|
||||
token_service,
|
||||
folder_service: None,
|
||||
}
|
||||
}
|
||||
@@ -127,11 +129,23 @@ impl AuthApplicationService {
|
||||
1024 * 1024 * 1024 // 1GB para usuarios normales
|
||||
};
|
||||
|
||||
// Crear usuario
|
||||
// Validar longitud de password antes de hashear
|
||||
if dto.password.len() < 8 {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"User",
|
||||
"Password debe tener al menos 8 caracteres"
|
||||
));
|
||||
}
|
||||
|
||||
// Hashear el password usando el servicio de infraestructura
|
||||
let password_hash = self.password_hasher.hash_password(&dto.password)?;
|
||||
|
||||
// Crear usuario con el hash pre-generado
|
||||
let user = User::new(
|
||||
dto.username.clone(),
|
||||
dto.email,
|
||||
dto.password,
|
||||
password_hash,
|
||||
role,
|
||||
quota,
|
||||
).map_err(|e| DomainError::new(
|
||||
@@ -203,13 +217,8 @@ impl AuthApplicationService {
|
||||
));
|
||||
}
|
||||
|
||||
// Verificar contraseña
|
||||
let is_valid = user.verify_password(&dto.password)
|
||||
.map_err(|_| DomainError::new(
|
||||
ErrorKind::AccessDenied,
|
||||
"Auth",
|
||||
"Credenciales inválidas"
|
||||
))?;
|
||||
// Verificar contraseña usando el hasher inyectado
|
||||
let is_valid = self.password_hasher.verify_password(&dto.password, user.password_hash())?;
|
||||
|
||||
if !is_valid {
|
||||
return Err(DomainError::new(
|
||||
@@ -223,11 +232,10 @@ impl AuthApplicationService {
|
||||
user.register_login();
|
||||
self.user_storage.update_user(user.clone()).await?;
|
||||
|
||||
// Generar tokens
|
||||
let access_token = self.auth_service.generate_access_token(&user)
|
||||
.map_err(DomainError::from)?;
|
||||
// Generar tokens usando el servicio de tokens inyectado
|
||||
let access_token = self.token_service.generate_access_token(&user)?;
|
||||
|
||||
let refresh_token = self.auth_service.generate_refresh_token();
|
||||
let refresh_token = self.token_service.generate_refresh_token();
|
||||
|
||||
// Guardar sesión
|
||||
let session = Session::new(
|
||||
@@ -235,7 +243,7 @@ impl AuthApplicationService {
|
||||
refresh_token.clone(),
|
||||
None, // IP (se puede añadir desde la capa HTTP)
|
||||
None, // User-Agent (se puede añadir desde la capa HTTP)
|
||||
self.auth_service.refresh_token_expiry_days(),
|
||||
self.token_service.refresh_token_expiry_days(),
|
||||
);
|
||||
|
||||
self.session_storage.create_session(session).await?;
|
||||
@@ -246,7 +254,7 @@ impl AuthApplicationService {
|
||||
access_token,
|
||||
refresh_token,
|
||||
token_type: "Bearer".to_string(),
|
||||
expires_in: self.auth_service.refresh_token_expiry_secs(),
|
||||
expires_in: self.token_service.refresh_token_expiry_secs(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -283,10 +291,9 @@ impl AuthApplicationService {
|
||||
self.session_storage.revoke_session(session.id()).await?;
|
||||
|
||||
// Generar nuevos tokens
|
||||
let access_token = self.auth_service.generate_access_token(&user)
|
||||
.map_err(DomainError::from)?;
|
||||
let access_token = self.token_service.generate_access_token(&user)?;
|
||||
|
||||
let new_refresh_token = self.auth_service.generate_refresh_token();
|
||||
let new_refresh_token = self.token_service.generate_refresh_token();
|
||||
|
||||
// Crear nueva sesión
|
||||
let new_session = Session::new(
|
||||
@@ -294,7 +301,7 @@ impl AuthApplicationService {
|
||||
new_refresh_token.clone(),
|
||||
None,
|
||||
None,
|
||||
self.auth_service.refresh_token_expiry_days(),
|
||||
self.token_service.refresh_token_expiry_days(),
|
||||
);
|
||||
|
||||
self.session_storage.create_session(new_session).await?;
|
||||
@@ -304,7 +311,7 @@ impl AuthApplicationService {
|
||||
access_token,
|
||||
refresh_token: new_refresh_token,
|
||||
token_type: "Bearer".to_string(),
|
||||
expires_in: self.auth_service.refresh_token_expiry_secs(),
|
||||
expires_in: self.token_service.refresh_token_expiry_secs(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -342,13 +349,8 @@ impl AuthApplicationService {
|
||||
// Obtener usuario
|
||||
let mut user = self.user_storage.get_user_by_id(user_id).await?;
|
||||
|
||||
// Verificar contraseña actual
|
||||
let is_valid = user.verify_password(&dto.current_password)
|
||||
.map_err(|_| DomainError::new(
|
||||
ErrorKind::AccessDenied,
|
||||
"Auth",
|
||||
"Contraseña actual incorrecta"
|
||||
))?;
|
||||
// Verificar contraseña actual usando el hasher inyectado
|
||||
let is_valid = self.password_hasher.verify_password(&dto.current_password, user.password_hash())?;
|
||||
|
||||
if !is_valid {
|
||||
return Err(DomainError::new(
|
||||
@@ -358,13 +360,18 @@ impl AuthApplicationService {
|
||||
));
|
||||
}
|
||||
|
||||
// Actualizar contraseña
|
||||
user.update_password(dto.new_password.clone())
|
||||
.map_err(|e| DomainError::new(
|
||||
// Validar nueva contraseña
|
||||
if dto.new_password.len() < 8 {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"User",
|
||||
format!("Error al cambiar contraseña: {}", e)
|
||||
))?;
|
||||
"Password debe tener al menos 8 caracteres"
|
||||
));
|
||||
}
|
||||
|
||||
// Hashear nueva contraseña y actualizar usuario
|
||||
let new_hash = self.password_hasher.hash_password(&dto.new_password)?;
|
||||
user.update_password_hash(new_hash);
|
||||
|
||||
// Guardar usuario actualizado
|
||||
self.user_storage.update_user(user).await?;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use futures::{future::join_all, Future};
|
||||
use tokio::sync::Semaphore;
|
||||
use tracing::{info, error};
|
||||
use tracing::info;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::application::services::file_service::FileService;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::application::ports::inbound::FolderUseCase;
|
||||
@@ -15,7 +14,6 @@ use crate::application::dtos::folder_dto::FolderDto;
|
||||
|
||||
/// Errores específicos para operaciones por lotes
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(dead_code)]
|
||||
pub enum BatchOperationError {
|
||||
#[error("Error de dominio: {0}")]
|
||||
Domain(#[from] DomainError),
|
||||
@@ -59,52 +57,6 @@ pub struct BatchStats {
|
||||
pub max_concurrency: usize,
|
||||
}
|
||||
|
||||
/// Tipo de entidad para operaciones por lotes
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum EntityType {
|
||||
File,
|
||||
Folder,
|
||||
}
|
||||
|
||||
/// Tipo de operación por lotes
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum BatchOperationType {
|
||||
Create,
|
||||
Read,
|
||||
Update,
|
||||
Delete,
|
||||
Copy,
|
||||
Move,
|
||||
}
|
||||
|
||||
/// Identificador para una entidad (ID o ruta)
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum EntityIdentifier {
|
||||
Id(String),
|
||||
Path(StoragePath),
|
||||
}
|
||||
|
||||
impl EntityIdentifier {
|
||||
#[allow(dead_code)]
|
||||
pub fn as_id(&self) -> Option<&str> {
|
||||
match self {
|
||||
EntityIdentifier::Id(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn as_path(&self) -> Option<&StoragePath> {
|
||||
match self {
|
||||
EntityIdentifier::Path(path) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Servicio de operaciones por lotes
|
||||
pub struct BatchOperationService {
|
||||
file_service: Arc<FileService>,
|
||||
@@ -494,7 +446,6 @@ impl BatchOperationService {
|
||||
}
|
||||
|
||||
/// Operación genérica de lote para cualquier tipo de función asíncrona
|
||||
#[allow(dead_code)]
|
||||
pub async fn generic_batch_operation<T, F, Fut>(
|
||||
&self,
|
||||
items: Vec<T>,
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::application::dtos::calendar_dto::{
|
||||
CalendarDto, CalendarEventDto, CreateCalendarDto, UpdateCalendarDto,
|
||||
CreateEventDto, UpdateEventDto, CreateEventICalDto
|
||||
};
|
||||
use crate::application::ports::calendar_ports::{CalendarStoragePort, CalendarUseCase};
|
||||
use crate::interfaces::middleware::auth::CurrentUser;
|
||||
use crate::common::errors::{DomainError, ErrorKind};
|
||||
|
||||
pub struct CalendarService {
|
||||
|
||||
@@ -9,12 +9,11 @@ use crate::application::dtos::address_book_dto::{
|
||||
};
|
||||
use crate::application::dtos::contact_dto::{
|
||||
ContactDto, CreateContactDto, UpdateContactDto, CreateContactVCardDto,
|
||||
ContactGroupDto, CreateContactGroupDto, UpdateContactGroupDto, GroupMembershipDto,
|
||||
EmailDto, PhoneDto, AddressDto
|
||||
ContactGroupDto, CreateContactGroupDto, UpdateContactGroupDto, GroupMembershipDto
|
||||
};
|
||||
use crate::application::ports::carddav_ports::{AddressBookUseCase, ContactUseCase};
|
||||
use crate::application::ports::storage_ports::StorageUseCase;
|
||||
use crate::common::errors::{DomainError, ErrorContext};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::entities::contact::{AddressBook, Contact, ContactGroup, Email, Phone, Address};
|
||||
use crate::domain::repositories::address_book_repository::AddressBookRepository;
|
||||
use crate::domain::repositories::contact_repository::{ContactRepository, ContactGroupRepository};
|
||||
|
||||
@@ -16,13 +16,6 @@ impl FileManagementService {
|
||||
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
|
||||
Self { file_repository }
|
||||
}
|
||||
|
||||
/// Creates a stub for testing
|
||||
pub fn default_stub() -> Self {
|
||||
Self {
|
||||
file_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -18,13 +18,6 @@ impl FileRetrievalService {
|
||||
pub fn new(file_repository: Arc<dyn FileReadPort>) -> Self {
|
||||
Self { file_repository }
|
||||
}
|
||||
|
||||
/// Crea un stub para pruebas
|
||||
pub fn default_stub() -> Self {
|
||||
Self {
|
||||
file_repository: Arc::new(crate::infrastructure::repositories::FileFsReadRepository::default_stub())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -48,14 +48,6 @@ impl FileUploadService {
|
||||
self.storage_usage_service = Some(storage_usage_service);
|
||||
self
|
||||
}
|
||||
|
||||
/// Crea un stub para pruebas
|
||||
pub fn default_stub() -> Self {
|
||||
Self {
|
||||
file_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub()),
|
||||
storage_usage_service: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -23,14 +23,6 @@ impl AppFileUseCaseFactory {
|
||||
file_write_repository,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un stub para pruebas
|
||||
pub fn default_stub() -> Self {
|
||||
Self {
|
||||
file_read_repository: Arc::new(crate::infrastructure::repositories::FileFsReadRepository::default_stub()),
|
||||
file_write_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileUseCaseFactory for AppFileUseCaseFactory {
|
||||
|
||||
@@ -51,7 +51,6 @@ impl I18nApplicationService {
|
||||
}
|
||||
|
||||
/// Load translations for all available locales
|
||||
#[allow(dead_code)]
|
||||
pub async fn load_all_translations(&self) -> Vec<(Locale, I18nResult<()>)> {
|
||||
let locales = self.i18n_service.available_locales().await;
|
||||
let mut results = Vec::new();
|
||||
@@ -70,7 +69,6 @@ impl I18nApplicationService {
|
||||
}
|
||||
|
||||
/// Check if a locale is supported
|
||||
#[allow(dead_code)]
|
||||
pub async fn is_supported(&self, locale: Locale) -> bool {
|
||||
self.i18n_service.is_supported(locale).await
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use thiserror::Error;
|
||||
use crate::domain::entities::folder::Folder;
|
||||
use crate::domain::repositories::folder_repository::{FolderRepository, FolderRepositoryError};
|
||||
use crate::domain::repositories::file_repository::FileRepositoryError;
|
||||
use crate::domain::services::path_service::{PathService, StoragePath};
|
||||
use crate::application::ports::outbound::IdMappingPort;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::application::ports::outbound::{IdMappingPort, StoragePort};
|
||||
|
||||
/// Errores específicos del mediador de almacenamiento
|
||||
#[derive(Debug, Error)]
|
||||
@@ -99,12 +99,12 @@ pub trait StorageMediator: Send + Sync + 'static {
|
||||
/// Implementación concreta del mediador de almacenamiento
|
||||
pub struct FileSystemStorageMediator {
|
||||
pub folder_repository: Arc<dyn FolderRepository>,
|
||||
pub path_service: Arc<PathService>,
|
||||
pub path_service: Arc<dyn StoragePort>,
|
||||
pub id_mapping: Arc<dyn IdMappingPort>,
|
||||
}
|
||||
|
||||
impl FileSystemStorageMediator {
|
||||
pub fn new(folder_repository: Arc<dyn FolderRepository>, path_service: Arc<PathService>, id_mapping: Arc<dyn IdMappingPort>) -> Self {
|
||||
pub fn new(folder_repository: Arc<dyn FolderRepository>, path_service: Arc<dyn StoragePort>, id_mapping: Arc<dyn IdMappingPort>) -> Self {
|
||||
Self { folder_repository, path_service, id_mapping }
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ impl FileSystemStorageMediator {
|
||||
/// Overload para implementar inicialización diferida con repository placeholder
|
||||
pub fn new_with_lazy_folder(
|
||||
_folder_repository: Arc<RwLock<Option<Arc<dyn FolderRepository>>>>,
|
||||
path_service: Arc<PathService>,
|
||||
path_service: Arc<dyn StoragePort>,
|
||||
id_mapping: Arc<dyn IdMappingPort>
|
||||
) -> Self {
|
||||
// Create temporary stub repository
|
||||
@@ -208,16 +208,18 @@ impl FolderRepository for FolderRepositoryStub {
|
||||
}
|
||||
|
||||
/// Stub implementation for initialization dependency issues
|
||||
pub struct StubStorageMediator {
|
||||
#[allow(dead_code)]
|
||||
_path_service: Arc<PathService>,
|
||||
}
|
||||
/// This is a minimal stub that doesn't require any infrastructure dependencies
|
||||
pub struct StubStorageMediator;
|
||||
|
||||
impl StubStorageMediator {
|
||||
pub fn new() -> Self {
|
||||
let root_path = PathBuf::from("/tmp");
|
||||
let path_service = Arc::new(PathService::new(root_path));
|
||||
Self { _path_service: path_service }
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StubStorageMediator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||
use tokio::task;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::application::ports::auth_ports::UserStoragePort;
|
||||
use crate::domain::repositories::file_repository::FileRepository;
|
||||
use crate::application::ports::outbound::FileStoragePort;
|
||||
use crate::application::ports::storage_ports::StorageUsagePort;
|
||||
use tracing::{info, error, debug};
|
||||
|
||||
@@ -14,14 +14,14 @@ use tracing::{info, error, debug};
|
||||
* is using and updating this information in the user records.
|
||||
*/
|
||||
pub struct StorageUsageService {
|
||||
file_repository: Arc<dyn FileRepository>,
|
||||
file_repository: Arc<dyn FileStoragePort>,
|
||||
user_repository: Arc<dyn UserStoragePort>,
|
||||
}
|
||||
|
||||
impl StorageUsageService {
|
||||
/// Creates a new storage usage service
|
||||
pub fn new(
|
||||
file_repository: Arc<dyn FileRepository>,
|
||||
file_repository: Arc<dyn FileStoragePort>,
|
||||
user_repository: Arc<dyn UserStoragePort>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -90,7 +90,7 @@ impl StorageUsageService {
|
||||
async fn calculate_folder_size(&self, folder_id: &str) -> Result<i64, DomainError> {
|
||||
// Implementation with explicit boxing to handle recursion in async functions
|
||||
async fn inner_calculate_size(
|
||||
repo: Arc<dyn FileRepository>,
|
||||
repo: Arc<dyn FileStoragePort>,
|
||||
folder_id: &str,
|
||||
) -> Result<i64, DomainError> {
|
||||
let mut total_size: i64 = 0;
|
||||
|
||||
Reference in New Issue
Block a user