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:
Dionisio
2026-02-02 23:56:40 +01:00
parent 6aceb07f3f
commit 52840e57df
88 changed files with 4286 additions and 4847 deletions
+7 -8
View File
@@ -1,8 +1,7 @@
use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
use sqlx::types::Uuid;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct AddressBook {
pub id: Uuid,
pub name: String,
@@ -29,21 +28,21 @@ impl Default for AddressBook {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct Email {
pub email: String,
pub r#type: String, // home, work, other
pub is_primary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct Phone {
pub number: String,
pub r#type: String, // mobile, home, work, fax, other
pub is_primary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct Address {
pub street: Option<String>,
pub city: Option<String>,
@@ -54,7 +53,7 @@ pub struct Address {
pub is_primary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct Contact {
pub id: Uuid,
pub address_book_id: Uuid,
@@ -105,7 +104,7 @@ impl Default for Contact {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct ContactGroup {
pub id: Uuid,
pub address_book_id: Uuid,
+3 -9
View File
@@ -1,4 +1,3 @@
use serde::{Serialize, Deserialize};
use crate::domain::services::path_service::StoragePath;
/**
@@ -15,7 +14,6 @@ pub enum FileError {
/// Occurs when validation fails for any file entity attribute.
#[error("Validation error: {0}")]
#[allow(dead_code)]
ValidationError(String),
}
@@ -36,7 +34,7 @@ pub type FileResult<T> = Result<T, FileError>;
* This entity maintains both physical storage information and logical metadata about files,
* serving as the bridge between the storage system and the application.
*/
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct File {
/// Unique identifier for the file - used throughout the system for file operations
id: String,
@@ -44,12 +42,10 @@ pub struct File {
/// Name of the file including extension
name: String,
/// Path to the file in the domain model - not serialized as it contains internal representation
#[serde(skip_serializing, skip_deserializing)]
/// Path to the file in the domain model
storage_path: StoragePath,
/// String representation of the path for serialization and API compatibility
#[serde(rename = "path")]
/// String representation of the path for API compatibility
path_string: String,
/// Size of the file in bytes
@@ -253,7 +249,6 @@ impl File {
// Methods to create new versions of the file (immutable)
/// Creates a new version of the file with updated name
#[allow(dead_code)]
pub fn with_name(&self, new_name: String) -> FileResult<Self> {
// Validate file name
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
@@ -318,7 +313,6 @@ impl File {
}
/// Creates a new version of the file with updated size
#[allow(dead_code)]
pub fn with_size(&self, new_size: u64) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
+2 -7
View File
@@ -1,4 +1,3 @@
use serde::{Serialize, Deserialize};
use crate::domain::services::path_service::StoragePath;
/// Error in the creation or manipulation of folder entities
@@ -8,7 +7,6 @@ pub enum FolderError {
InvalidFolderName(String),
#[error("Validation error: {0}")]
#[allow(dead_code)]
ValidationError(String),
}
@@ -16,7 +14,7 @@ pub enum FolderError {
pub type FolderResult<T> = Result<T, FolderError>;
/// Represents a folder entity in the domain
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Folder {
/// Unique identifier for the folder
id: String,
@@ -25,11 +23,9 @@ pub struct Folder {
name: String,
/// Path to the folder in the domain model
#[serde(skip_serializing, skip_deserializing)]
storage_path: StoragePath,
/// String representation of the path (for serialization compatibility)
#[serde(rename = "path")]
/// String representation of the path (for API compatibility)
path_string: String,
/// Parent folder ID (None if it's a root folder)
@@ -235,7 +231,6 @@ impl Folder {
}
/// Returns an absolute path for this folder
#[allow(dead_code)]
pub fn get_absolute_path<P: AsRef<std::path::Path>>(&self, root_path: P) -> std::path::PathBuf {
let mut result = std::path::PathBuf::from(root_path.as_ref());
+1 -2
View File
@@ -1,8 +1,7 @@
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use chrono::{DateTime, Utc, Duration};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct Session {
pub id: String,
pub user_id: String,
+23 -40
View File
@@ -1,7 +1,3 @@
use serde::{Serialize, Deserialize};
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use argon2::password_hash::SaltString;
use rand_core::OsRng;
use uuid::Uuid;
use chrono::{DateTime, Utc};
@@ -22,7 +18,7 @@ pub enum UserError {
pub type UserResult<T> = Result<T, UserError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
// We'll handle conversion manually for now until the type is properly set up in the database
pub enum UserRole {
Admin,
@@ -38,12 +34,11 @@ impl std::fmt::Display for UserRole {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone)]
pub struct User {
id: String,
username: String,
email: String,
#[serde(skip_serializing)]
password_hash: String,
role: UserRole,
storage_quota_bytes: i64,
@@ -55,10 +50,22 @@ pub struct User {
}
impl User {
/// Create a new user with a pre-hashed password.
///
/// The password hashing should be done externally using PasswordHasherPort
/// to maintain clean architecture and keep cryptographic dependencies
/// out of the domain layer.
///
/// # Arguments
/// * `username` - User's username (3-32 characters)
/// * `email` - User's email address
/// * `password_hash` - Pre-hashed password (from PasswordHasherPort)
/// * `role` - User's role
/// * `storage_quota_bytes` - Storage quota in bytes
pub fn new(
username: String,
email: String,
password: String,
password_hash: String,
role: UserRole,
storage_quota_bytes: i64,
) -> UserResult<Self> {
@@ -75,19 +82,12 @@ impl User {
)));
}
if password.len() < 8 {
if password_hash.is_empty() {
return Err(UserError::InvalidPassword(format!(
"Password debe tener al menos 8 caracteres"
"Password hash no puede estar vacío"
)));
}
// Generar hash con Argon2id (recomendado para 2023+)
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2.hash_password(password.as_bytes(), &salt)
.map_err(|e| UserError::ValidationError(format!("Error al generar hash: {}", e)))?
.to_string();
let now = Utc::now();
Ok(Self {
@@ -179,30 +179,13 @@ impl User {
&self.password_hash
}
// Verificación de password
pub fn verify_password(&self, password: &str) -> UserResult<bool> {
let parsed_hash = PasswordHash::new(&self.password_hash)
.map_err(|e| UserError::AuthenticationError(format!("Error al procesar hash: {}", e)))?;
Ok(Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok())
}
// Cambiar contraseña
pub fn update_password(&mut self, new_password: String) -> UserResult<()> {
if new_password.len() < 8 {
return Err(UserError::InvalidPassword(format!(
"Password debe tener al menos 8 caracteres"
)));
}
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
self.password_hash = argon2.hash_password(new_password.as_bytes(), &salt)
.map_err(|e| UserError::ValidationError(format!("Error al generar hash: {}", e)))?
.to_string();
/// Update the password hash.
///
/// The new password should be hashed externally using PasswordHasherPort
/// before calling this method.
pub fn update_password_hash(&mut self, new_hash: String) {
self.password_hash = new_hash;
self.updated_at = Utc::now();
Ok(())
}
// Actualizar uso de almacenamiento