use crate::models::computed_permission::PermissionScopeType; use crate::models::{ channel, channel_role_permission, channel_user_permission, computed_permission, role_user, server_role_permission, server_user, server_user_permission, }; use crate::permissions::{ChannelPermission, ServerPermission}; use crate::services::ServicesContext; use sea_orm::{ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QuerySelect, Set}; use std::collections::HashMap; use std::sync::Arc; use uuid::Uuid; #[derive(Debug, Clone)] pub struct PermissionService { service_context: Arc, } impl PermissionService { pub fn new(service_context: Arc) -> Self { Self { service_context } } /// Recalcule de manière globale et optimisée `computed_permission` pour TOUS les utilisateurs /// et TOUTES les ressources d'un serveur donné. pub async fn sync_server(&self, server_id: Uuid) -> Result<(), anyhow::Error> { let db = &self.service_context.repositories.server.context.db; Self::sync_server_with_db(db, server_id).await } /// Recalcule TOUTES les entrées `computed_permission` d'un utilisateur sur un serveur. pub async fn sync_user(&self, user_id: Uuid, server_id: Uuid) -> Result<(), anyhow::Error> { let db = &self.service_context.repositories.server.context.db; Self::sync_user_with_db(db, user_id, server_id).await } /// Recalcule le `computed_permission` pour UN utilisateur et UN canal spécifique. pub async fn sync_channel_user_permission( &self, channel_id: Uuid, user_id: Uuid, ) -> Result<(), anyhow::Error> { let db = &self.service_context.repositories.server.context.db; Self::sync_channel_user_permission_with_db(db, channel_id, user_id).await } } impl PermissionService { pub async fn sync_server_with_db(db: &C, server_id: Uuid) -> Result<(), anyhow::Error> where C: ConnectionTrait, { // 1. Charger tous les canaux du serveur let channels = channel::Entity::find() .filter(channel::Column::ServerId.eq(server_id)) .all(db) .await?; let channel_ids: Vec = channels.iter().map(|c| c.id).collect(); // 2. Charger tous les utilisateurs du serveur let user_ids: Vec = server_user::Entity::find() .filter(server_user::Column::ServerId.eq(server_id)) .select_only() .column(server_user::Column::UserId) .into_tuple::() .all(db) .await?; if user_ids.is_empty() { // Aucun membre, nettoyer simplement le cache de ce serveur computed_permission::Entity::delete_many() .filter(computed_permission::Column::ServerId.eq(server_id)) .exec(db) .await?; return Ok(()); } // 3. Charger en lot toutes les affectations de rôles des membres let user_roles_models = role_user::Entity::find() .filter(role_user::Column::UserId.is_in(user_ids.clone())) .all(db) .await?; let mut roles_by_user: HashMap> = HashMap::new(); for ur in user_roles_models { roles_by_user .entry(ur.user_id) .or_default() .push(ur.role_id); } // 4. Charger toutes les permissions serveur (Rôles & Utilisateurs) let server_role_perms = server_role_permission::Entity::find() .filter(server_role_permission::Column::ServerId.eq(server_id)) .all(db) .await?; let mut server_perm_by_role: HashMap = HashMap::new(); for srp in server_role_perms { server_perm_by_role.insert( srp.role_id, ServerPermission::from_bits_retain(srp.permissions as u64), ); } let server_user_perms = server_user_permission::Entity::find() .filter(server_user_permission::Column::ServerId.eq(server_id)) .filter(server_user_permission::Column::UserId.is_in(user_ids.clone())) .all(db) .await?; let mut server_perm_by_user: HashMap = HashMap::new(); for sup in server_user_perms { server_perm_by_user.insert( sup.user_id, ServerPermission::from_bits_retain(sup.permissions as u64), ); } // 5. Charger toutes les permissions de canaux (Rôles & Utilisateurs) let channel_role_perms = if channel_ids.is_empty() { Vec::new() } else { channel_role_permission::Entity::find() .filter(channel_role_permission::Column::ChannelId.is_in(channel_ids.clone())) .all(db) .await? }; let mut channel_role_perm_map: HashMap<(Uuid, Uuid), ChannelPermission> = HashMap::new(); for crp in channel_role_perms { channel_role_perm_map.insert( (crp.channel_id, crp.role_id), ChannelPermission::from_bits_retain(crp.permissions as u64), ); } let channel_user_perms = if channel_ids.is_empty() { Vec::new() } else { channel_user_permission::Entity::find() .filter(channel_user_permission::Column::ChannelId.is_in(channel_ids.clone())) .filter(channel_user_permission::Column::UserId.is_in(user_ids.clone())) .all(db) .await? }; let mut channel_user_perm_map: HashMap<(Uuid, Uuid), ChannelPermission> = HashMap::new(); for cup in channel_user_perms { channel_user_perm_map.insert( (cup.channel_id, cup.user_id), ChannelPermission::from_bits_retain(cup.permissions as u64), ); } // 6. Calcul en RAM pour l'ensemble des paires (Utilisateur x Ressource) let mut to_insert: Vec = Vec::new(); for user_id in user_ids { let user_roles = roles_by_user.get(&user_id); // A. Permission Serveur let mut final_server_perm = ServerPermission::empty(); if let Some(roles) = user_roles { for role_id in roles { if let Some(p) = server_perm_by_role.get(role_id) { final_server_perm |= *p; } } } if let Some(p) = server_perm_by_user.get(&user_id) { final_server_perm |= *p; } to_insert.push(computed_permission::ActiveModel { user_id: Set(user_id), server_id: Set(server_id), scope_type: Set(PermissionScopeType::Server), resource_id: Set(server_id), permissions: Set(final_server_perm.bits() as i64), ..Default::default() }); // B. Permissions par Canal for channel in &channels { let mut final_channel_perm = ChannelPermission::empty(); if let Some(roles) = user_roles { for role_id in roles { if let Some(p) = channel_role_perm_map.get(&(channel.id, *role_id)) { final_channel_perm |= *p; } } } if let Some(p) = channel_user_perm_map.get(&(channel.id, user_id)) { final_channel_perm |= *p; } to_insert.push(computed_permission::ActiveModel { user_id: Set(user_id), server_id: Set(server_id), scope_type: Set(PermissionScopeType::Channel), resource_id: Set(channel.id), permissions: Set(final_channel_perm.bits() as i64), ..Default::default() }); } } // 7. Remplacement atomique complet pour le serveur en BDD computed_permission::Entity::delete_many() .filter(computed_permission::Column::ServerId.eq(server_id)) .exec(db) .await?; if !to_insert.is_empty() { // Insertion par lots (chunking de 1000 pour éviter les limites de paramètres SQL) for chunk in to_insert.chunks(1000) { computed_permission::Entity::insert_many(chunk.to_vec()) .exec(db) .await?; } } Ok(()) } pub async fn sync_user_with_db( db: &C, user_id: Uuid, server_id: Uuid, ) -> Result<(), anyhow::Error> where C: ConnectionTrait, { // 1. Rôles de l'utilisateur let role_ids = role_user::Entity::find() .filter(role_user::Column::UserId.eq(user_id)) .select_only() .column(role_user::Column::RoleId) .into_tuple::() .all(db) .await?; // 2. Permissions serveur des rôles let mut server_permissions = ServerPermission::empty(); if !role_ids.is_empty() { let role_permissions = server_role_permission::Entity::find() .filter(server_role_permission::Column::ServerId.eq(server_id)) .filter(server_role_permission::Column::RoleId.is_in(role_ids.clone())) .all(db) .await?; for permission in role_permissions { server_permissions |= ServerPermission::from_bits_retain(permission.permissions as u64); } } // 3. Permissions serveur directes if let Some(permission) = server_user_permission::Entity::find() .filter(server_user_permission::Column::ServerId.eq(server_id)) .filter(server_user_permission::Column::UserId.eq(user_id)) .one(db) .await? { server_permissions |= ServerPermission::from_bits_retain(permission.permissions as u64); } // 4. Canaux du serveur let channels = channel::Entity::find() .filter(channel::Column::ServerId.eq(server_id)) .all(db) .await?; let channel_ids: Vec = channels.iter().map(|c| c.id).collect(); // 5. Permissions de rôles pour les canaux let role_channel_permissions = if role_ids.is_empty() || channel_ids.is_empty() { Vec::new() } else { channel_role_permission::Entity::find() .filter(channel_role_permission::Column::ChannelId.is_in(channel_ids.clone())) .filter(channel_role_permission::Column::RoleId.is_in(role_ids)) .all(db) .await? }; let mut permissions_by_channel: HashMap = HashMap::new(); for permission in role_channel_permissions { permissions_by_channel .entry(permission.channel_id) .or_default() .insert(ChannelPermission::from_bits_retain( permission.permissions as u64, )); } // 6. Permissions directes utilisateur pour les canaux let user_channel_permissions = if channel_ids.is_empty() { Vec::new() } else { channel_user_permission::Entity::find() .filter(channel_user_permission::Column::UserId.eq(user_id)) .filter(channel_user_permission::Column::ChannelId.is_in(channel_ids)) .all(db) .await? }; for permission in user_channel_permissions { permissions_by_channel .entry(permission.channel_id) .or_default() .insert(ChannelPermission::from_bits_retain( permission.permissions as u64, )); } // 7. Modèles à insérer let mut computed_permissions = Vec::with_capacity(channels.len().saturating_add(1)); computed_permissions.push(computed_permission::ActiveModel { user_id: Set(user_id), server_id: Set(server_id), scope_type: Set(PermissionScopeType::Server), resource_id: Set(server_id), permissions: Set(server_permissions.bits() as i64), ..Default::default() }); for channel in channels { let channel_permissions = permissions_by_channel .remove(&channel.id) .unwrap_or_else(ChannelPermission::empty); computed_permissions.push(computed_permission::ActiveModel { user_id: Set(user_id), server_id: Set(server_id), scope_type: Set(PermissionScopeType::Channel), resource_id: Set(channel.id), permissions: Set(channel_permissions.bits() as i64), ..Default::default() }); } // 8. Remplacement atomique BDD computed_permission::Entity::delete_many() .filter(computed_permission::Column::UserId.eq(user_id)) .filter(computed_permission::Column::ServerId.eq(server_id)) .exec(db) .await?; if !computed_permissions.is_empty() { computed_permission::Entity::insert_many(computed_permissions) .exec(db) .await?; } Ok(()) } pub async fn sync_channel_user_permission_with_db( db: &C, channel_id: Uuid, user_id: Uuid, ) -> Result<(), anyhow::Error> where C: ConnectionTrait, { // 1. Récupérer le canal pour connaître son server_id let channel = channel::Entity::find_by_id(channel_id) .one(db) .await? .ok_or_else(|| anyhow::anyhow!("Canal non trouvé"))?; let server_id = match channel.server_id { Some(sid) => sid, None => return Ok(()), // Canal DM ou sans serveur }; // 2. Rôles de l'utilisateur sur le serveur let role_ids = role_user::Entity::find() .filter(role_user::Column::UserId.eq(user_id)) .select_only() .column(role_user::Column::RoleId) .into_tuple::() .all(db) .await?; // 3. Permissions cumulées des rôles sur ce canal let mut computed = ChannelPermission::empty(); if !role_ids.is_empty() { let role_perms = channel_role_permission::Entity::find() .filter(channel_role_permission::Column::ChannelId.eq(channel_id)) .filter(channel_role_permission::Column::RoleId.is_in(role_ids)) .all(db) .await?; for p in role_perms { computed |= ChannelPermission::from_bits_retain(p.permissions as u64); } } // 4. Override direct utilisateur sur le canal if let Some(user_perm) = channel_user_permission::Entity::find() .filter(channel_user_permission::Column::ChannelId.eq(channel_id)) .filter(channel_user_permission::Column::UserId.eq(user_id)) .one(db) .await? { computed |= ChannelPermission::from_bits_retain(user_perm.permissions as u64); } // 5. Suppression & insertion atomique de l'entrée computed_permission computed_permission::Entity::delete_many() .filter(computed_permission::Column::UserId.eq(user_id)) .filter(computed_permission::Column::ResourceId.eq(channel_id)) .exec(db) .await?; let active_model = computed_permission::ActiveModel { user_id: Set(user_id), server_id: Set(server_id), scope_type: Set(PermissionScopeType::Channel), resource_id: Set(channel_id), permissions: Set(computed.bits() as i64), ..Default::default() }; computed_permission::Entity::insert(active_model) .exec(db) .await?; Ok(()) } }