//! Dual-mode repository pattern implementation //! Provides both PostgreSQL and in-memory repository implementations //! with seamless switching between modes for testing and production use std::sync::Arc; use async_trait::async_trait; use uuid::Uuid; use chrono::{DateTime, Utc}; use thiserror::Error; use sea_orm::{DbErr, ActiveModelTrait, ModelTrait, EntityTrait, QueryFilter, ColumnTrait, PaginatorTrait, QuerySelect, ActiveValue::Set,}; use crate::postgres::{PostgresConnection, PostgresError}; use imphnen_entities::seaorm::auth::users::{Entity as UsersEntity, Model as UserModel}; use imphnen_entities::seaorm::auth::roles::{Entity as RolesEntity, Model as RoleModel}; /// Error types for dual-mode repository operations #[derive(Debug, Error)] pub enum PostgresRepositoryError { #[error("Database error: {0}")] DatabaseError(#[from] DbErr), #[error("PostgreSQL connection error: {0}")] ConnectionError(#[from] PostgresError), #[error("Entity not found: {0}")] NotFound(String), #[error("Validation error: {0}")] ValidationError(String), #[error("Conversion error: {0}")] ConversionError(String), #[error("Repository operation failed: {0}")] OperationFailed(String), } /// Repository trait for user operations #[async_trait] pub trait PostgresRepository { /// Find user by ID async fn find_user_by_id(&self, id: Uuid) -> Result, PostgresRepositoryError>; /// Find user by email async fn find_user_by_email(&self, email: &str) -> Result, PostgresRepositoryError>; /// Find user by username async fn find_user_by_username(&self, username: &str) -> Result, PostgresRepositoryError>; /// Create new user async fn create_user(&self, user: UserModel) -> Result; /// Update user async fn update_user(&self, id: Uuid, user: UserModel) -> Result; /// Delete user async fn delete_user(&self, id: Uuid) -> Result<(), PostgresRepositoryError>; /// List all users with pagination async fn list_users(&self, offset: u64, limit: u64) -> Result, PostgresRepositoryError>; /// Count total users async fn count_users(&self) -> Result; /// Find role by ID async fn find_role_by_id(&self, id: Uuid) -> Result, PostgresRepositoryError>; /// Find role by name async fn find_role_by_name(&self, name: &str) -> Result, PostgresRepositoryError>; /// Create new role async fn create_role(&self, role: RoleModel) -> Result; /// Update role async fn update_role(&self, id: Uuid, role: RoleModel) -> Result; /// Delete role async fn delete_role(&self, id: Uuid) -> Result<(), PostgresRepositoryError>; /// List all roles async fn list_roles(&self) -> Result, PostgresRepositoryError>; } /// Default PostgreSQL repository implementation pub struct PostgresRepositoryDefaultImpl { connection: Arc, } impl PostgresRepositoryDefaultImpl { /// Create a new PostgreSQL repository instance pub fn new(connection: Arc) -> Self { Self { connection } } /// Get the underlying PostgreSQL connection pub fn connection(&self) -> &Arc { &self.connection } } #[async_trait] impl PostgresRepository for PostgresRepositoryDefaultImpl { async fn find_user_by_id(&self, id: Uuid) -> Result, PostgresRepositoryError> { let user = UsersEntity::find_by_id(id) .one(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(user) } async fn find_user_by_email(&self, email: &str) -> Result, PostgresRepositoryError> { let user = UsersEntity::find() .filter(imphnen_entities::seaorm::auth::users::Column::Email.eq(email)) .one(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(user) } async fn find_user_by_username(&self, username: &str) -> Result, PostgresRepositoryError> { let user = UsersEntity::find() .filter(imphnen_entities::seaorm::auth::users::Column::Username.eq(username)) .one(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(user) } async fn create_user(&self, user: UserModel) -> Result { let active_model = imphnen_entities::seaorm::auth::users::ActiveModel { id: Set(user.id), email: Set(user.email), password_hash: Set(user.password_hash), username: Set(user.username), first_name: Set(user.first_name), last_name: Set(user.last_name), avatar_url: Set(user.avatar_url), is_verified: Set(user.is_verified), is_active: Set(user.is_active), metadata: Set(user.metadata), created_at: Set(user.created_at), updated_at: Set(user.updated_at), deleted_at: Set(user.deleted_at), // Use user.deleted_at role_id: Set(user.role_id), }; let created_user = active_model.insert(&self.connection.conn).await.map_err(PostgresRepositoryError::DatabaseError)?; Ok(created_user) } async fn update_user(&self, id: Uuid, user: UserModel) -> Result { let existing_user = self.find_user_by_id(id).await? .ok_or_else(|| PostgresRepositoryError::NotFound(format!("User with id {id} not found")))?; let mut active_model: imphnen_entities::seaorm::auth::users::ActiveModel = existing_user.into(); // Update fields active_model.email = Set(user.email); active_model.password_hash = Set(user.password_hash); active_model.username = Set(user.username); active_model.first_name = Set(user.first_name); active_model.last_name = Set(user.last_name); active_model.avatar_url = Set(user.avatar_url); active_model.is_verified = Set(user.is_verified); active_model.is_active = Set(user.is_active); active_model.metadata = Set(user.metadata); active_model.updated_at = Set(user.updated_at); active_model.deleted_at = Set(user.deleted_at); active_model.role_id = Set(user.role_id); // Update role_id let result = active_model .update(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(result) } async fn delete_user(&self, id: Uuid) -> Result<(), PostgresRepositoryError> { let user = self.find_user_by_id(id).await? .ok_or_else(|| PostgresRepositoryError::NotFound(format!("User with id {id} not found")))?; user.delete(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(()) } async fn list_users(&self, offset: u64, limit: u64) -> Result, PostgresRepositoryError> { let users = UsersEntity::find() .offset(offset) .limit(limit) .all(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(users) } async fn count_users(&self) -> Result { let count = UsersEntity::find() .count(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(count) } async fn find_role_by_id(&self, id: Uuid) -> Result, PostgresRepositoryError> { let role = RolesEntity::find_by_id(id) .one(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(role) } async fn find_role_by_name(&self, name: &str) -> Result, PostgresRepositoryError> { let role = RolesEntity::find() .filter(imphnen_entities::seaorm::auth::roles::Column::Name.eq(name)) .one(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(role) } async fn create_role(&self, role: RoleModel) -> Result { let active_model = imphnen_entities::seaorm::auth::roles::ActiveModel { id: Set(role.id), name: Set(role.name), description: Set(role.description), permissions: Set(role.permissions), is_system_role: Set(role.is_system_role), is_default: Set(role.is_default), created_at: Set(role.created_at), updated_at: Set(role.updated_at), deleted_at: Set(role.deleted_at), }; let result = active_model .insert(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(result) } async fn update_role(&self, id: Uuid, role: RoleModel) -> Result { let existing_role = self.find_role_by_id(id).await? .ok_or_else(|| PostgresRepositoryError::NotFound(format!("Role with id {id} not found")))?; let mut active_model: imphnen_entities::seaorm::auth::roles::ActiveModel = existing_role.into(); active_model.name = Set(role.name); active_model.description = Set(role.description); active_model.permissions = Set(role.permissions); active_model.is_system_role = Set(role.is_system_role); active_model.is_default = Set(role.is_default); active_model.updated_at = Set(role.updated_at); active_model.deleted_at = Set(role.deleted_at); let result = active_model .update(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(result) } async fn delete_role(&self, id: Uuid) -> Result<(), PostgresRepositoryError> { let role = self.find_role_by_id(id).await? .ok_or_else(|| PostgresRepositoryError::NotFound(format!("Role with id {id} not found")))?; role.delete(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(()) } async fn list_roles(&self) -> Result, PostgresRepositoryError> { let roles = RolesEntity::find() .all(&self.connection.conn) .await .map_err(PostgresRepositoryError::DatabaseError)?; Ok(roles) } } /// Conversion utilities for data transformation between different formats pub mod conversion_utils { use super::*; use serde_json::Value; /// Convert JSON value to PostgreSQL-compatible format pub fn json_to_pg_json(json: Value) -> Result { Ok(json) } /// Convert PostgreSQL JSON to standard JSON value pub fn pg_json_to_json(pg_json: serde_json::Value) -> Result { Ok(pg_json) } /// Convert string to PostgreSQL UUID format pub fn string_to_uuid(uuid_str: &str) -> Result { Uuid::parse_str(uuid_str) .map_err(|e| PostgresRepositoryError::ConversionError(format!("Invalid UUID: {e}"))) } /// Convert DateTime to PostgreSQL timestamp format pub fn datetime_to_pg_timestamp(dt: DateTime) -> String { dt.format("%Y-%m-%d %H:%M:%S%.3f").to_string() } /// Convert PostgreSQL timestamp string to DateTime pub fn pg_timestamp_to_datetime(pg_timestamp: &str) -> Result, PostgresRepositoryError> { DateTime::parse_from_rfc3339(pg_timestamp) .map(|dt| dt.with_timezone(&Utc)) .map_err(|e| PostgresRepositoryError::ConversionError(format!("Invalid timestamp: {e}"))) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_conversion_utils() { // Test UUID conversion let uuid_str = "550e8400-e29b-41d4-a716-446655440000"; let uuid = conversion_utils::string_to_uuid(uuid_str).unwrap(); assert_eq!(uuid.to_string(), uuid_str); // Test timestamp conversion let now = Utc::now(); let pg_timestamp = conversion_utils::datetime_to_pg_timestamp(now); assert!(!pg_timestamp.is_empty()); } #[test] fn test_postgres_repository_error() { let error = PostgresRepositoryError::NotFound("Test error".to_string()); assert_eq!(error.to_string(), "Entity not found: Test error"); let error = PostgresRepositoryError::ValidationError("Invalid input".to_string()); assert_eq!(error.to_string(), "Validation error: Invalid input"); } }