use std::sync::Arc; use tracing::info; use uuid::Uuid; use validator::Validate; use crate::error::{auth, http, ControlCenterError, Result}; use crate::models::{CreateRoleRequest, Role, RoleResponse, UpdateRoleRequest}; use crate::services::DatabaseService; /// Role service for managing role operations #[derive(Clone)] pub struct RoleService { db: Arc, } impl RoleService { /// Create a new role service pub fn new(db: Arc) -> Self { Self { db } } /// Create a new role pub async fn create_role(&self, request: CreateRoleRequest) -> Result { // Validate request request .validate() .map_err(|e| ControlCenterError::Http(http::HttpError::Validation(e.to_string())))?; // Check if role already exists if self.find_by_name(&request.name).await?.is_some() { let msg = format!("Role with name '{}' already exists", request.name); return Err(ControlCenterError::Http(http::conflict(&msg))); } // Create role let role = Role::new( request.name, request.description, request.permissions, false, // User-created roles are not system roles ); // Save to database let created_role: Role = self.db .db .create("roles") .content(role) .await? .ok_or_else(|| { ControlCenterError::from(crate::error::database::DatabaseError::Database( "Failed to create role".to_string(), )) })?; info!("Created role: {}", created_role.name); Ok(RoleResponse::from(created_role)) } /// Get role by ID pub async fn get_by_id(&self, role_id: Uuid) -> Result { let query = "SELECT * FROM roles WHERE role_id = $role_id"; let mut result = self.db.db.query(query).bind(("role_id", role_id)).await?; let roles: Vec = result.take(0)?; roles.into_iter().next().ok_or_else(|| { ControlCenterError::Auth(auth::AuthError::RoleNotFound(role_id.to_string())) }) } /// Get role response by ID (without sensitive data) pub async fn get_role_response_by_id(&self, role_id: Uuid) -> Result { let role = self.get_by_id(role_id).await?; Ok(RoleResponse::from(role)) } /// Find role by name pub async fn find_by_name(&self, name: &str) -> Result> { let query = "SELECT * FROM roles WHERE name = $name"; let mut result = self .db .db .query(query) .bind(("name", name.to_string())) .await?; let roles: Vec = result.take(0)?; Ok(roles.into_iter().next()) } /// Update role pub async fn update_role( &self, role_id: Uuid, request: UpdateRoleRequest, ) -> Result { // Validate request request .validate() .map_err(|e| ControlCenterError::Http(http::HttpError::Validation(e.to_string())))?; // Get existing role let mut role = self.get_by_id(role_id).await?; // Check if it's a system role if role.is_system { return Err(ControlCenterError::Http(http::HttpError::BadRequest( "Cannot modify system roles".to_string(), ))); } // Check for conflicts if name is being changed if let Some(ref new_name) = request.name { if new_name != &role.name && self.find_by_name(new_name).await?.is_some() { let msg = format!("Role with name '{}' already exists", new_name); return Err(ControlCenterError::Http(http::conflict(&msg))); } } // Update role role.update(request); // Save to database let query = "UPDATE roles SET name = $name, description = $description, permissions = $permissions, updated_at = $updated_at WHERE role_id = $role_id"; // Clone data before using in database operations to avoid borrow issues let name = role.name.clone(); let description = role.description.clone(); let permissions = role.permissions.clone(); let updated_at = role.updated_at; self.db .db .query(query) .bind(("name", name)) .bind(("description", description)) .bind(("permissions", permissions)) .bind(("updated_at", updated_at)) .bind(("role_id", role_id)) .await?; info!("Updated role: {}", role.name); Ok(RoleResponse::from(role)) } /// Delete role pub async fn delete_role(&self, role_id: Uuid) -> Result<()> { // Get existing role let role = self.get_by_id(role_id).await?; // Check if it's a system role if role.is_system { return Err(ControlCenterError::Http(http::HttpError::BadRequest( "Cannot delete system roles".to_string(), ))); } // Check if role is in use by any users let user_count = self.get_users_with_role_count(&role.name).await?; if user_count > 0 { let msg = format!( "Cannot delete role '{}' - it is assigned to {} users", role.name, user_count ); return Err(ControlCenterError::Http(http::conflict(&msg))); } // Delete role let query = "DELETE roles WHERE role_id = $role_id"; self.db.db.query(query).bind(("role_id", role_id)).await?; info!("Deleted role: {}", role.name); Ok(()) } /// List all roles pub async fn list_roles( &self, include_system: Option, limit: Option, offset: Option, ) -> Result> { let mut query = "SELECT * FROM roles".to_string(); if let Some(include_system) = include_system { if !include_system { query.push_str(" WHERE is_system = false"); } } query.push_str(" ORDER BY created_at DESC"); if let Some(limit) = limit { query.push_str(&format!(" LIMIT {}", limit)); } if let Some(offset) = offset { query.push_str(&format!(" START {}", offset)); } let mut result = self.db.db.query(&query).await?; let roles: Vec = result.take(0)?; Ok(roles.into_iter().map(RoleResponse::from).collect()) } /// Add permission to role pub async fn add_permission(&self, role_id: Uuid, permission: String) -> Result { let mut role = self.get_by_id(role_id).await?; // Check if it's a system role if role.is_system { return Err(ControlCenterError::Http(http::HttpError::BadRequest( "Cannot modify system roles".to_string(), ))); } // Add permission if not already present role.add_permission(permission.clone()); // Save to database let query = "UPDATE roles SET permissions = $permissions, updated_at = $updated_at WHERE \ role_id = $role_id"; let permissions = role.permissions.clone(); let updated_at = role.updated_at; self.db .db .query(query) .bind(("permissions", permissions)) .bind(("updated_at", updated_at)) .bind(("role_id", role_id)) .await?; info!("Added permission '{}' to role: {}", permission, role.name); Ok(RoleResponse::from(role)) } /// Remove permission from role pub async fn remove_permission( &self, role_id: Uuid, permission: String, ) -> Result { let mut role = self.get_by_id(role_id).await?; // Check if it's a system role if role.is_system { return Err(ControlCenterError::Http(http::HttpError::BadRequest( "Cannot modify system roles".to_string(), ))); } // Remove permission role.remove_permission(&permission); // Save to database let query = "UPDATE roles SET permissions = $permissions, updated_at = $updated_at WHERE \ role_id = $role_id"; let permissions = role.permissions.clone(); let updated_at = role.updated_at; self.db .db .query(query) .bind(("permissions", permissions)) .bind(("updated_at", updated_at)) .bind(("role_id", role_id)) .await?; info!( "Removed permission '{}' from role: {}", permission, role.name ); Ok(RoleResponse::from(role)) } /// Get count of users with a specific role async fn get_users_with_role_count(&self, role_name: &str) -> Result { let query = "SELECT count() FROM users WHERE $role_name IN roles"; let mut result = self .db .db .query(query) .bind(("role_name", role_name.to_string())) .await?; let counts: Vec = result.take(0)?; Ok(counts.into_iter().next().unwrap_or(0)) } /// Get role count pub async fn get_role_count(&self, include_system: Option) -> Result { let mut query = "SELECT count() FROM roles".to_string(); if let Some(include_system) = include_system { if !include_system { query.push_str(" WHERE is_system = false"); } } let mut result = self.db.db.query(&query).await?; let counts: Vec = result.take(0)?; Ok(counts.into_iter().next().unwrap_or(0)) } /// Get roles by names pub async fn get_roles_by_names(&self, role_names: &[String]) -> Result> { if role_names.is_empty() { return Ok(Vec::new()); } let query = "SELECT * FROM roles WHERE name IN $role_names"; let mut result = self .db .db .query(query) .bind(("role_names", role_names.to_vec())) .await?; let roles: Vec = result.take(0)?; Ok(roles) } /// Check if role has permission pub async fn has_permission(&self, role_name: &str, permission: &str) -> Result { if let Some(role) = self.find_by_name(role_name).await? { Ok(role.has_permission(permission)) } else { Ok(false) } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_role_request_validation() { // Valid request let valid_request = CreateRoleRequest { name: "test_role".to_string(), description: Some("Test role description".to_string()), permissions: Some(vec!["read".to_string(), "write".to_string()]), }; assert!(valid_request.validate().is_ok()); // Short name let short_name = CreateRoleRequest { name: "a".to_string(), description: None, permissions: None, }; assert!(short_name.validate().is_err()); // Long name let long_name = CreateRoleRequest { name: "a".repeat(51), description: None, permissions: None, }; assert!(long_name.validate().is_err()); // Long description let long_description = CreateRoleRequest { name: "test_role".to_string(), description: Some("a".repeat(201)), permissions: None, }; assert!(long_description.validate().is_err()); } #[test] fn test_update_role_request_validation() { // Valid request let valid_request = UpdateRoleRequest { name: Some("updated_role".to_string()), description: Some("Updated description".to_string()), permissions: Some(vec!["read".to_string()]), }; assert!(valid_request.validate().is_ok()); // Short name let short_name = UpdateRoleRequest { name: Some("a".to_string()), description: None, permissions: None, }; assert!(short_name.validate().is_err()); // Valid empty update let empty_update = UpdateRoleRequest { name: None, description: None, permissions: None, }; assert!(empty_update.validate().is_ok()); } }