use std::sync::Arc; use chrono::Utc; use domain::ports::{PasswordHasher, UserRepository}; use domain::user::{validate_email, validate_password, NewUser, Role, User, UserUpdate}; use domain::DomainError; use uuid::Uuid; pub struct UserService { users: Arc, hasher: Arc, } impl UserService { pub fn new(users: Arc, hasher: Arc) -> Self { Self { users, hasher } } pub async fn list(&self) -> Result, DomainError> { self.users.list().await } pub async fn get(&self, id: Uuid) -> Result { self.users .find_by_id(id) .await? .ok_or(DomainError::NotFound) } pub async fn create(&self, new: NewUser) -> Result { let email = new.email.trim().to_lowercase(); validate_email(&email)?; validate_password(&new.password)?; if self.users.find_by_email(&email).await?.is_some() { return Err(DomainError::EmailTaken); } let user = User { id: Uuid::new_v4(), email, display_name: new.display_name.trim().to_string(), password_hash: self.hasher.hash(&new.password)?, role: new.role, is_active: true, created_at: Utc::now(), }; self.users.insert(&user).await?; Ok(user) } pub async fn update(&self, id: Uuid, update: UserUpdate) -> Result { let current = self.get(id).await?; let loses_admin = current.is_admin() && current.is_active && (update.role == Some(Role::User) || update.is_active == Some(false)); if loses_admin && self.users.count_active_admins().await? <= 1 { return Err(DomainError::LastAdmin); } self.users.update(id, &update).await } pub async fn reset_password(&self, id: Uuid, password: &str) -> Result<(), DomainError> { validate_password(password)?; self.get(id).await?; self.users .set_password_hash(id, &self.hasher.hash(password)?) .await } /// Create the initial admin if the user table is empty. Returns true if created. pub async fn bootstrap_admin(&self, email: &str, password: &str) -> Result { if self.users.count().await? > 0 { return Ok(false); } self.create(NewUser { email: email.into(), display_name: "Administrator".into(), password: password.into(), role: Role::Admin, }) .await?; Ok(true) } }