role_service.rs 210 lignes · 6552 octets
use crate::error::AppError;
use crate::models::{role, user_role};
use chrono::Utc;
use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set};
use uuid::Uuid;

/// Service for managing roles in the RBAC system
///
/// This service provides business logic for role operations,
/// including creating predefined roles (admin, user).
pub struct RoleService;

impl RoleService {
    /// Create a new role with the given name and description
    ///
    /// # Arguments
    ///
    /// * `db` - Database connection
    /// * `name` - Role name (e.g., "admin", "user")
    /// * `description` - Optional description of the role
    ///
    /// # Returns
    ///
    /// The created role model
    ///
    /// # Errors
    ///
    /// Returns `AppError::DbErr` if database operation fails
    /// Returns `AppError::Conflict` if role with same name already exists
    pub async fn create_role(
        db: &DatabaseConnection,
        name: String,
        description: Option<String>,
    ) -> Result<role::Model, AppError> {
        let now = Utc::now();

        let role = role::ActiveModel {
            id: Set(Uuid::new_v4()),
            name: Set(name),
            description: Set(description),
            created_at: Set(now),
            updated_at: Set(now),
        };

        let result = role.insert(db).await?;
        Ok(result)
    }

    /// Get a role by its name
    ///
    /// # Arguments
    ///
    /// * `db` - Database connection
    /// * `name` - Role name to search for
    ///
    /// # Returns
    ///
    /// The role model if found, None otherwise
    ///
    /// # Errors
    ///
    /// Returns `AppError::DbErr` if database operation fails
    pub async fn get_role_by_name(
        db: &DatabaseConnection,
        name: &str,
    ) -> Result<Option<role::Model>, AppError> {
        use sea_orm::ColumnTrait;
        use sea_orm::QueryFilter;

        let role = role::Entity::find()
            .filter(role::Column::Name.eq(name))
            .one(db)
            .await?;

        Ok(role)
    }

    /// Initialize predefined roles (admin, user, app_manager)
    ///
    /// This function creates the default roles if they don't exist.
    /// It's idempotent - can be called multiple times safely.
    ///
    /// # Arguments
    ///
    /// * `db` - Database connection
    ///
    /// # Returns
    ///
    /// A tuple of (admin_role, user_role, app_manager_role)
    ///
    /// # Errors
    ///
    /// Returns `AppError::Database` if database operation fails
    pub async fn initialize_default_roles(
        db: &DatabaseConnection,
    ) -> Result<(role::Model, role::Model, role::Model), AppError> {
        // Create admin role if it doesn't exist
        let admin_role = match Self::get_role_by_name(db, "admin").await? {
            Some(role) => role,
            None => {
                tracing::info!("Creating default 'admin' role");
                Self::create_role(
                    db,
                    "admin".to_string(),
                    Some("Administrator with full access".to_string()),
                )
                .await?
            }
        };

        // Create user role if it doesn't exist
        let user_role = match Self::get_role_by_name(db, "user").await? {
            Some(role) => role,
            None => {
                tracing::info!("Creating default 'user' role");
                Self::create_role(
                    db,
                    "user".to_string(),
                    Some("Standard user with basic access".to_string()),
                )
                .await?
            }
        };

        // Create app_manager role if it doesn't exist (Feature 013)
        let app_manager_role = match Self::get_role_by_name(db, "app_manager").await? {
            Some(role) => role,
            None => {
                tracing::info!("Creating default 'app_manager' role");
                Self::create_role(
                    db,
                    "app_manager".to_string(),
                    Some("Can manage (enable/disable) applications".to_string()),
                )
                .await?
            }
        };

        Ok((admin_role, user_role, app_manager_role))
    }

    /// Return all roles stored in the database.
    pub async fn list_all_roles(db: &DatabaseConnection) -> Result<Vec<role::Model>, AppError> {
        let roles = role::Entity::find().all(db).await?;
        Ok(roles)
    }

    /// Return a single role by its UUID, or `None` if not found.
    pub async fn get_role_by_id(
        db: &DatabaseConnection,
        id: Uuid,
    ) -> Result<Option<role::Model>, AppError> {
        let role = role::Entity::find_by_id(id).one(db).await?;
        Ok(role)
    }

    /// Update the name and/or description of an existing role.
    pub async fn update_role(
        db: &DatabaseConnection,
        id: Uuid,
        name: Option<String>,
        description: Option<String>,
    ) -> Result<role::Model, AppError> {
        let existing = Self::get_role_by_id(db, id)
            .await?
            .ok_or_else(|| AppError::NotFound("Role not found".to_string()))?;

        let mut active: role::ActiveModel = existing.into();

        if let Some(n) = name {
            active.name = Set(n);
        }
        active.description = Set(description);
        active.updated_at = Set(Utc::now());

        let updated = active.update(db).await?;
        Ok(updated)
    }

    /// Delete a role by its UUID. Returns an error if the role is protected (`admin` or `user`).
    pub async fn delete_role(db: &DatabaseConnection, id: Uuid) -> Result<(), AppError> {
        let existing = Self::get_role_by_id(db, id)
            .await?
            .ok_or_else(|| AppError::NotFound("Role not found".to_string()))?;

        let protected = ["admin", "user"];
        if protected.contains(&existing.name.as_str()) {
            return Err(AppError::Validation(format!(
                "Cannot delete protected role '{}'",
                existing.name
            )));
        }

        role::Entity::delete_by_id(id).exec(db).await?;
        Ok(())
    }

    /// Count the number of users assigned to a role.
    pub async fn count_users_for_role(
        db: &DatabaseConnection,
        role_id: Uuid,
    ) -> Result<u64, AppError> {
        use sea_orm::PaginatorTrait;
        let count = user_role::Entity::find()
            .filter(user_role::Column::RoleId.eq(role_id))
            .count(db)
            .await?;
        Ok(count)
    }
}