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 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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user