init
This commit is contained in:
@@ -746,6 +746,7 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(Alias::new("computed_permission"))
|
.table(Alias::new("computed_permission"))
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
|
.col(ColumnDef::new(Alias::new("id")).uuid().not_null())
|
||||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||||
.col(ColumnDef::new(Alias::new("server_id")).uuid().not_null())
|
.col(ColumnDef::new(Alias::new("server_id")).uuid().not_null())
|
||||||
.col(
|
.col(
|
||||||
@@ -762,6 +763,7 @@ impl MigrationTrait for Migration {
|
|||||||
)
|
)
|
||||||
.primary_key(
|
.primary_key(
|
||||||
Index::create()
|
Index::create()
|
||||||
|
.col(Alias::new("id"))
|
||||||
.col(Alias::new("user_id"))
|
.col(Alias::new("user_id"))
|
||||||
.col(Alias::new("server_id"))
|
.col(Alias::new("server_id"))
|
||||||
.col(Alias::new("scope_type"))
|
.col(Alias::new("scope_type"))
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ pub struct CategoryQueryParams {
|
|||||||
|
|
||||||
impl Default for CategoryQueryParams {
|
impl Default for CategoryQueryParams {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self { server_id: None }
|
||||||
server_id: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,4 +34,9 @@ pub struct CategoryResponse {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
|
|
||||||
|
/// None : contexte sans permissions (champ ignoré dans le JSON).
|
||||||
|
/// Some(value) : valeur de computed_permission (0 si absente).
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub permission: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ pub struct ChannelResponse {
|
|||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
|
|
||||||
|
/// None : contexte sans permissions (champ ignoré dans le JSON).
|
||||||
|
/// Some(value) : valeur de computed_permission (0 si absente).
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub permission: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ mod server;
|
|||||||
mod server_item_order;
|
mod server_item_order;
|
||||||
mod server_tree;
|
mod server_tree;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
mod user;
|
pub mod user;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RepositoryContext {
|
pub struct RepositoryContext {
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
use crate::models::{category, channel, computed_permission, server_item_order};
|
use crate::models::{category, channel, computed_permission, server_item_order};
|
||||||
use crate::permissions::ChannelPermission;
|
use crate::permissions::ChannelPermission;
|
||||||
|
use crate::repositories::types::{CategoryWithPermissions, ChannelWithPermissions, ServerTreeData};
|
||||||
use crate::repositories::{AnyResult, RepositoryContext};
|
use crate::repositories::{AnyResult, RepositoryContext};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, JoinType, QueryFilter, QuerySelect};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ChannelWithPermissions {
|
|
||||||
pub channel: channel::Model,
|
|
||||||
pub permissions: ChannelPermission,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ServerTreeData {
|
|
||||||
pub orders: Vec<server_item_order::Model>,
|
|
||||||
pub categories: Vec<category::Model>,
|
|
||||||
pub channels: Vec<ChannelWithPermissions>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ServerTreeRepository {
|
pub struct ServerTreeRepository {
|
||||||
pub context: Arc<RepositoryContext>,
|
pub context: Arc<RepositoryContext>,
|
||||||
@@ -25,7 +14,7 @@ pub struct ServerTreeRepository {
|
|||||||
|
|
||||||
impl ServerTreeRepository {
|
impl ServerTreeRepository {
|
||||||
pub async fn get_for_user(&self, server_id: Uuid, user_id: Uuid) -> AnyResult<ServerTreeData> {
|
pub async fn get_for_user(&self, server_id: Uuid, user_id: Uuid) -> AnyResult<ServerTreeData> {
|
||||||
let (orders, categories, channel_rows) = tokio::try_join!(
|
let (orders, categories_models, channel_models, computed_permissions) = tokio::try_join!(
|
||||||
async {
|
async {
|
||||||
Ok::<_, anyhow::Error>(
|
Ok::<_, anyhow::Error>(
|
||||||
server_item_order::Entity::find()
|
server_item_order::Entity::find()
|
||||||
@@ -42,16 +31,53 @@ impl ServerTreeRepository {
|
|||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
self.find_channels_with_permissions(server_id, user_id),
|
async {
|
||||||
|
Ok::<_, anyhow::Error>(
|
||||||
|
channel::Entity::find()
|
||||||
|
.filter(channel::Column::ServerId.eq(server_id))
|
||||||
|
.all(&self.context.db)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
Ok::<_, anyhow::Error>(
|
||||||
|
computed_permission::Entity::find()
|
||||||
|
.filter(computed_permission::Column::UserId.eq(user_id))
|
||||||
|
.filter(computed_permission::Column::ServerId.eq(server_id))
|
||||||
|
.all(&self.context.db)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let channels = channel_rows
|
let perm_map: HashMap<Uuid, u64> = computed_permissions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(channel, permission)| {
|
.map(|cp| (cp.resource_id, cp.permissions as u64))
|
||||||
permission.map(|permission| ChannelWithPermissions {
|
.collect();
|
||||||
channel,
|
|
||||||
permissions: ChannelPermission::from_bits_retain(permission.permissions as u64),
|
let categories = categories_models
|
||||||
|
.into_iter()
|
||||||
|
.map(|category| {
|
||||||
|
let permissions = perm_map
|
||||||
|
.get(&category.id)
|
||||||
|
.map(|&p| ChannelPermission::from_bits_retain(p));
|
||||||
|
CategoryWithPermissions {
|
||||||
|
category,
|
||||||
|
permissions,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let channels = channel_models
|
||||||
|
.into_iter()
|
||||||
|
.map(|channel| {
|
||||||
|
let permissions = perm_map
|
||||||
|
.get(&channel.id)
|
||||||
|
.map(|&p| ChannelPermission::from_bits_retain(p));
|
||||||
|
ChannelWithPermissions {
|
||||||
|
channel,
|
||||||
|
permissions,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -61,31 +87,4 @@ impl ServerTreeRepository {
|
|||||||
channels,
|
channels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_channels_with_permissions(
|
|
||||||
&self,
|
|
||||||
server_id: Uuid,
|
|
||||||
user_id: Uuid,
|
|
||||||
) -> AnyResult<Vec<(channel::Model, Option<computed_permission::Model>)>> {
|
|
||||||
// Construction dynamique de la relation personnalisée (channel.id = computed_permission.resource_id)
|
|
||||||
let channel_to_permission = channel::Entity::belongs_to(computed_permission::Entity)
|
|
||||||
.from(channel::Column::Id)
|
|
||||||
.to(computed_permission::Column::ResourceId)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let rows = channel::Entity::find()
|
|
||||||
.join(JoinType::InnerJoin, channel_to_permission)
|
|
||||||
.filter(channel::Column::ServerId.eq(server_id))
|
|
||||||
.filter(computed_permission::Column::UserId.eq(user_id))
|
|
||||||
.filter(computed_permission::Column::ServerId.eq(server_id))
|
|
||||||
.filter(
|
|
||||||
computed_permission::Column::ScopeType
|
|
||||||
.eq(computed_permission::PermissionScopeType::Channel),
|
|
||||||
)
|
|
||||||
.select_also(computed_permission::Entity)
|
|
||||||
.all(&self.context.db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(rows)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::models::{category, channel, computed_permission};
|
use crate::models::{category, channel, computed_permission, server_item_order};
|
||||||
|
use crate::permissions::ChannelPermission;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub enum ServerExplorerItem {
|
pub enum ServerExplorerItem {
|
||||||
@@ -56,3 +57,22 @@ impl PermissionResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CategoryWithPermissions {
|
||||||
|
pub category: category::Model,
|
||||||
|
pub permissions: Option<ChannelPermission>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChannelWithPermissions {
|
||||||
|
pub channel: channel::Model,
|
||||||
|
pub permissions: Option<ChannelPermission>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServerTreeData {
|
||||||
|
pub orders: Vec<server_item_order::Model>,
|
||||||
|
pub categories: Vec<CategoryWithPermissions>,
|
||||||
|
pub channels: Vec<ChannelWithPermissions>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
use crate::models::category;
|
|
||||||
use crate::domain::dto::category::{
|
use crate::domain::dto::category::{
|
||||||
CategoryResponse, CreateCategoryRequest, UpdateCategoryRequest,
|
CategoryResponse, CreateCategoryRequest, UpdateCategoryRequest,
|
||||||
};
|
};
|
||||||
|
use crate::models::category;
|
||||||
use sea_orm::Set;
|
use sea_orm::Set;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn category_model_to_category_response(model: category::Model) -> CategoryResponse {
|
pub fn category_model_to_category_response(model: category::Model) -> CategoryResponse {
|
||||||
|
category_model_to_category_response_with_permission(model, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn category_model_to_category_response_with_permission(
|
||||||
|
model: category::Model,
|
||||||
|
permission: Option<u64>,
|
||||||
|
) -> CategoryResponse {
|
||||||
CategoryResponse {
|
CategoryResponse {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
server_id: model.server_id,
|
server_id: model.server_id,
|
||||||
name: model.name,
|
name: model.name,
|
||||||
created_at: model.created_at,
|
created_at: model.created_at,
|
||||||
updated_at: model.updated_at,
|
updated_at: model.updated_at,
|
||||||
|
permission,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
use crate::models::{channel, channel_role_permission, channel_user_permission};
|
|
||||||
use crate::repositories::types::ChannelFilter;
|
|
||||||
use crate::domain::dto::channel::{
|
use crate::domain::dto::channel::{
|
||||||
ChannelQueryParams, ChannelResponse, ChannelRolePermissionResponse,
|
ChannelQueryParams, ChannelResponse, ChannelRolePermissionResponse,
|
||||||
ChannelUserPermissionResponse, CreateChannelRequest, UpdateChannelRequest,
|
ChannelUserPermissionResponse, CreateChannelRequest, UpdateChannelRequest,
|
||||||
};
|
};
|
||||||
|
use crate::models::{channel, channel_role_permission, channel_user_permission};
|
||||||
|
use crate::repositories::types::ChannelFilter;
|
||||||
use sea_orm::Set;
|
use sea_orm::Set;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn channel_model_to_channel_response(model: channel::Model) -> ChannelResponse {
|
pub fn channel_model_to_channel_response(model: channel::Model) -> ChannelResponse {
|
||||||
|
channel_model_to_channel_response_with_permission(model, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channel_model_to_channel_response_with_permission(
|
||||||
|
model: channel::Model,
|
||||||
|
permission: Option<u64>,
|
||||||
|
) -> ChannelResponse {
|
||||||
ChannelResponse {
|
ChannelResponse {
|
||||||
id: model.id,
|
id: model.id,
|
||||||
server_id: model.server_id,
|
server_id: model.server_id,
|
||||||
@@ -16,6 +23,7 @@ pub fn channel_model_to_channel_response(model: channel::Model) -> ChannelRespon
|
|||||||
name: model.name,
|
name: model.name,
|
||||||
created_at: model.created_at,
|
created_at: model.created_at,
|
||||||
updated_at: model.updated_at,
|
updated_at: model.updated_at,
|
||||||
|
permission,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ use crate::domain::dto::server::{
|
|||||||
CreateServerRequest, ServerResponse, ServerRolePermissionResponse, ServerTreeResponse,
|
CreateServerRequest, ServerResponse, ServerRolePermissionResponse, ServerTreeResponse,
|
||||||
ServerUserPermissionResponse, SetServerPermissionRequest, UpdateServerRequest,
|
ServerUserPermissionResponse, SetServerPermissionRequest, UpdateServerRequest,
|
||||||
};
|
};
|
||||||
use crate::http::context::Superuser;
|
use crate::http::context::{CurrentUser, Superuser};
|
||||||
use crate::http::error::HTTPError;
|
use crate::http::error::HTTPError;
|
||||||
use crate::repositories::types::ChannelFilter;
|
|
||||||
use crate::routes::server::mapper;
|
use crate::routes::server::mapper;
|
||||||
use axum::{
|
use axum::{
|
||||||
Json,
|
Json,
|
||||||
@@ -384,6 +383,7 @@ pub async fn remove_role_permission(
|
|||||||
tag = "Servers"
|
tag = "Servers"
|
||||||
)]
|
)]
|
||||||
pub async fn get_tree(
|
pub async fn get_tree(
|
||||||
|
user: CurrentUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(server_id): Path<Uuid>,
|
Path(server_id): Path<Uuid>,
|
||||||
) -> Result<Json<ServerTreeResponse>, HTTPError> {
|
) -> Result<Json<ServerTreeResponse>, HTTPError> {
|
||||||
@@ -395,20 +395,15 @@ pub async fn get_tree(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or(HTTPError::NotFound)?;
|
.ok_or(HTTPError::NotFound)?;
|
||||||
|
|
||||||
// Exécution parallèle des 3 requêtes via leurs repositories respectifs
|
let tree = state
|
||||||
let (orders, channels, categories) = tokio::try_join!(
|
|
||||||
state
|
|
||||||
.repositories
|
.repositories
|
||||||
.server_item_order
|
.server_tree
|
||||||
.get_by_server(server_id),
|
.get_for_user(server_id, user.id)
|
||||||
state.repositories.channel.filter(ChannelFilter {
|
.await?;
|
||||||
server_id: Some(server_id)
|
|
||||||
}),
|
|
||||||
state.repositories.category.get_by_server(server_id),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Assemblage du ServerTreeResponse via le mapper
|
Ok(Json(mapper::build_server_tree(
|
||||||
let tree = mapper::build_server_tree(orders, channels, categories);
|
tree.orders,
|
||||||
|
tree.channels,
|
||||||
Ok(Json(tree))
|
tree.categories,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-24
@@ -2,11 +2,10 @@ use crate::domain::dto::server::{
|
|||||||
CreateServerRequest, ServerExplorerItemResponse, ServerResponse, ServerRolePermissionResponse,
|
CreateServerRequest, ServerExplorerItemResponse, ServerResponse, ServerRolePermissionResponse,
|
||||||
ServerTreeResponse, ServerUserPermissionResponse, UpdateServerRequest,
|
ServerTreeResponse, ServerUserPermissionResponse, UpdateServerRequest,
|
||||||
};
|
};
|
||||||
use crate::models::{
|
use crate::models::{server, server_item_order, server_role_permission, server_user_permission};
|
||||||
category, channel, server, server_item_order, server_role_permission, server_user_permission,
|
use crate::repositories::types::{CategoryWithPermissions, ChannelWithPermissions};
|
||||||
};
|
use crate::routes::category::mapper::category_model_to_category_response_with_permission;
|
||||||
use crate::routes::category::mapper::category_model_to_category_response;
|
use crate::routes::channel::mapper::channel_model_to_channel_response_with_permission;
|
||||||
use crate::routes::channel::mapper::channel_model_to_channel_response;
|
|
||||||
use sea_orm::Set;
|
use sea_orm::Set;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@@ -65,44 +64,53 @@ pub fn server_role_permission_to_response(
|
|||||||
|
|
||||||
pub fn build_server_tree(
|
pub fn build_server_tree(
|
||||||
orders: Vec<server_item_order::Model>,
|
orders: Vec<server_item_order::Model>,
|
||||||
channels: Vec<channel::Model>,
|
channels: Vec<ChannelWithPermissions>,
|
||||||
categories: Vec<category::Model>,
|
categories: Vec<CategoryWithPermissions>,
|
||||||
) -> ServerTreeResponse {
|
) -> ServerTreeResponse {
|
||||||
// Map d'accès rapide : resource_id -> order_key
|
|
||||||
let order_map: HashMap<Uuid, i64> = orders
|
let order_map: HashMap<Uuid, i64> = orders
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|o| (o.resource_id, o.order_key))
|
.map(|order| (order.resource_id, order.order_key))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Regrouper les canaux par catégorie ou orphelins
|
let mut category_channels: HashMap<Uuid, Vec<ChannelWithPermissions>> = HashMap::new();
|
||||||
let mut category_channels: HashMap<Uuid, Vec<channel::Model>> = HashMap::new();
|
let mut orphan_channels: Vec<ChannelWithPermissions> = Vec::new();
|
||||||
let mut orphan_channels: Vec<channel::Model> = Vec::new();
|
|
||||||
|
|
||||||
for chan in channels {
|
for channel_with_permissions in channels {
|
||||||
if let Some(cat_id) = chan.category_id {
|
if let Some(category_id) = channel_with_permissions.channel.category_id {
|
||||||
category_channels.entry(cat_id).or_default().push(chan);
|
category_channels
|
||||||
|
.entry(category_id)
|
||||||
|
.or_default()
|
||||||
|
.push(channel_with_permissions);
|
||||||
} else {
|
} else {
|
||||||
orphan_channels.push(chan);
|
orphan_channels.push(channel_with_permissions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trier les canaux dans chaque catégorie par leur order_key
|
// Trier les canaux dans chaque catégorie par leur order_key
|
||||||
for chans in category_channels.values_mut() {
|
for chans in category_channels.values_mut() {
|
||||||
chans.sort_by_key(|c| order_map.get(&c.id).copied().unwrap_or(i64::MAX));
|
chans.sort_by_key(|c| order_map.get(&c.channel.id).copied().unwrap_or(i64::MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Préparer les éléments racine (catégories & canaux orphelins) avec leur order_key
|
// Préparer les éléments racine (catégories & canaux orphelins) avec leur order_key
|
||||||
let mut root_items: Vec<(ServerExplorerItemResponse, i64)> = Vec::new();
|
let mut root_items: Vec<(ServerExplorerItemResponse, i64)> = Vec::new();
|
||||||
|
|
||||||
for cat in categories {
|
for cat_with_perm in categories {
|
||||||
let cat_id = cat.id;
|
let cat_id = cat_with_perm.category.id;
|
||||||
let order_key = order_map.get(&cat_id).copied().unwrap_or(i64::MAX);
|
let order_key = order_map.get(&cat_id).copied().unwrap_or(i64::MAX);
|
||||||
let chans = category_channels.remove(&cat_id).unwrap_or_default();
|
let chans = category_channels.remove(&cat_id).unwrap_or_default();
|
||||||
|
|
||||||
let cat_response = category_model_to_category_response(cat);
|
let cat_perm_bits = cat_with_perm.permissions.map(|p| p.bits()).unwrap_or(0);
|
||||||
|
let cat_response = category_model_to_category_response_with_permission(
|
||||||
|
cat_with_perm.category,
|
||||||
|
Some(cat_perm_bits),
|
||||||
|
);
|
||||||
|
|
||||||
let chan_responses = chans
|
let chan_responses = chans
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(channel_model_to_channel_response)
|
.map(|c| {
|
||||||
|
let chan_perm_bits = c.permissions.map(|p| p.bits()).unwrap_or(0);
|
||||||
|
channel_model_to_channel_response_with_permission(c.channel, Some(chan_perm_bits))
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
root_items.push((
|
root_items.push((
|
||||||
@@ -111,9 +119,16 @@ pub fn build_server_tree(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
for chan in orphan_channels {
|
for chan_with_perm in orphan_channels {
|
||||||
let order_key = order_map.get(&chan.id).copied().unwrap_or(i64::MAX);
|
let order_key = order_map
|
||||||
let chan_response = channel_model_to_channel_response(chan);
|
.get(&chan_with_perm.channel.id)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(i64::MAX);
|
||||||
|
let chan_perm_bits = chan_with_perm.permissions.map(|p| p.bits()).unwrap_or(0);
|
||||||
|
let chan_response = channel_model_to_channel_response_with_permission(
|
||||||
|
chan_with_perm.channel,
|
||||||
|
Some(chan_perm_bits),
|
||||||
|
);
|
||||||
root_items.push((
|
root_items.push((
|
||||||
ServerExplorerItemResponse::Channel(chan_response),
|
ServerExplorerItemResponse::Channel(chan_response),
|
||||||
order_key,
|
order_key,
|
||||||
|
|||||||
+64
-25
@@ -1,9 +1,11 @@
|
|||||||
use crate::repositories::Repositories;
|
|
||||||
use crate::services::ServicesContext;
|
|
||||||
use crate::domain::dto::channel::{CreateChannelRequest, UpdateChannelRequest};
|
use crate::domain::dto::channel::{CreateChannelRequest, UpdateChannelRequest};
|
||||||
use crate::models::channel;
|
use crate::models::{channel, role};
|
||||||
use event_bus::Scope;
|
use crate::permissions::PermissionSet;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, QueryOrder, TransactionTrait, Set, PaginatorTrait};
|
use crate::services::ServicesContext;
|
||||||
|
use crate::services::permission::PermissionService;
|
||||||
|
use sea_orm::{
|
||||||
|
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set, TransactionTrait,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -24,10 +26,10 @@ impl ChannelService {
|
|||||||
let db = &self.service_context.repositories.server.context.db;
|
let db = &self.service_context.repositories.server.context.db;
|
||||||
let event_bus = &self.service_context.event_bus;
|
let event_bus = &self.service_context.event_bus;
|
||||||
|
|
||||||
// Start transaction
|
let channel = db
|
||||||
let txn = db.begin().await?;
|
.transaction::<_, channel::Model, anyhow::Error>(|txn| {
|
||||||
|
Box::pin(async move {
|
||||||
// 1. Insert channel within transaction
|
// 1. Insertion du canal
|
||||||
let active_model = channel::ActiveModel {
|
let active_model = channel::ActiveModel {
|
||||||
server_id: Set(payload.server_id),
|
server_id: Set(payload.server_id),
|
||||||
category_id: Set(payload.category_id),
|
category_id: Set(payload.category_id),
|
||||||
@@ -36,17 +38,23 @@ impl ChannelService {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let channel = active_model.insert(&txn).await?;
|
let channel = active_model.insert(txn).await?;
|
||||||
|
|
||||||
// 2. If server_id is present, insert into server_item_order within transaction
|
// 2. Si server_id est présent, enregistrement de l'ordre d'affichage
|
||||||
if let Some(server_id) = payload.server_id {
|
if let Some(server_id) = payload.server_id {
|
||||||
// Get max order key or determine order key
|
let max_order: Option<i64> =
|
||||||
let max_order: Option<i64> = crate::models::server_item_order::Entity::find()
|
crate::models::server_item_order::Entity::find()
|
||||||
.filter(crate::models::server_item_order::Column::ServerId.eq(server_id))
|
.filter(
|
||||||
|
crate::models::server_item_order::Column::ServerId
|
||||||
|
.eq(server_id),
|
||||||
|
)
|
||||||
.select_only()
|
.select_only()
|
||||||
.column_as(crate::models::server_item_order::Column::OrderKey.max(), "max_key")
|
.column_as(
|
||||||
|
crate::models::server_item_order::Column::OrderKey.max(),
|
||||||
|
"max_key",
|
||||||
|
)
|
||||||
.into_tuple::<Option<i64>>()
|
.into_tuple::<Option<i64>>()
|
||||||
.one(&txn)
|
.one(txn)
|
||||||
.await?
|
.await?
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
@@ -55,16 +63,43 @@ impl ChannelService {
|
|||||||
let order_item = crate::models::server_item_order::ActiveModel {
|
let order_item = crate::models::server_item_order::ActiveModel {
|
||||||
server_id: Set(server_id),
|
server_id: Set(server_id),
|
||||||
resource_id: Set(channel.id),
|
resource_id: Set(channel.id),
|
||||||
resource_type: Set(crate::models::server_item_order::OrderedResourceType::Channel),
|
resource_type: Set(
|
||||||
|
crate::models::server_item_order::OrderedResourceType::Channel,
|
||||||
|
),
|
||||||
parent_category_id: Set(payload.category_id),
|
parent_category_id: Set(payload.category_id),
|
||||||
order_key: Set(next_order),
|
order_key: Set(next_order),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
order_item.insert(&txn).await?;
|
order_item.insert(txn).await?;
|
||||||
|
|
||||||
|
// 3. Attribution des permissions par défaut au rôle par défaut
|
||||||
|
if let Some(default_role) = role::Entity::find()
|
||||||
|
.filter(role::Column::ServerId.eq(server_id))
|
||||||
|
.filter(role::Column::IsDefault.eq(true))
|
||||||
|
.one(txn)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let default_channel_permissions =
|
||||||
|
PermissionSet::DEFAULT.channel.bits() as i64;
|
||||||
|
|
||||||
|
let role_perm = crate::models::channel_role_permission::ActiveModel {
|
||||||
|
channel_id: Set(channel.id),
|
||||||
|
role_id: Set(default_role.id),
|
||||||
|
permissions: Set(default_channel_permissions),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
role_perm.insert(txn).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit transaction
|
// 4. Refresh des permissions (Global pour l'instant)
|
||||||
txn.commit().await?;
|
// TODO: Cibler/focaliser le spectre du recalcul (ex: uniquement les membres impactés ou le canal spécifique)
|
||||||
|
PermissionService::sync_server_with_db(txn, server_id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(channel)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Post-commit event emission
|
// Post-commit event emission
|
||||||
event_bus.emit("channel_created", channel.clone());
|
event_bus.emit("channel_created", channel.clone());
|
||||||
@@ -126,9 +161,7 @@ impl ChannelService {
|
|||||||
.exec(&txn)
|
.exec(&txn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let res = channel::Entity::delete_by_id(id)
|
let res = channel::Entity::delete_by_id(id).exec(&txn).await?;
|
||||||
.exec(&txn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let deleted = res.rows_affected > 0;
|
let deleted = res.rows_affected > 0;
|
||||||
|
|
||||||
@@ -173,7 +206,10 @@ impl ChannelService {
|
|||||||
|
|
||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
|
|
||||||
event_bus.emit("channel_user_permission_created", (channel_id, user_id, permissions));
|
event_bus.emit(
|
||||||
|
"channel_user_permission_created",
|
||||||
|
(channel_id, user_id, permissions),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -233,7 +269,10 @@ impl ChannelService {
|
|||||||
|
|
||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
|
|
||||||
event_bus.emit("channel_role_permission_created", (channel_id, role_id, permissions));
|
event_bus.emit(
|
||||||
|
"channel_role_permission_created",
|
||||||
|
(channel_id, role_id, permissions),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-10
@@ -1,23 +1,25 @@
|
|||||||
use crate::repositories::Repositories;
|
use crate::repositories::Repositories;
|
||||||
use crate::services::permission_sync::PermissionSyncService;
|
|
||||||
use crate::services::server_order::ServerOrderService;
|
|
||||||
use crate::services::channel::ChannelService;
|
|
||||||
use crate::services::server::ServerService;
|
|
||||||
use crate::services::category::CategoryService;
|
use crate::services::category::CategoryService;
|
||||||
|
use crate::services::channel::ChannelService;
|
||||||
use crate::services::message::MessageService;
|
use crate::services::message::MessageService;
|
||||||
use crate::services::user::UserService;
|
use crate::services::permission::PermissionService;
|
||||||
|
use crate::services::permission_sync::PermissionSyncService;
|
||||||
use crate::services::role::RoleService;
|
use crate::services::role::RoleService;
|
||||||
|
use crate::services::server::ServerService;
|
||||||
|
use crate::services::server_order::ServerOrderService;
|
||||||
|
use crate::services::user::UserService;
|
||||||
use event_bus::EventBus;
|
use event_bus::EventBus;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
pub mod permission_sync;
|
|
||||||
mod server_order;
|
|
||||||
pub mod channel;
|
|
||||||
pub mod server;
|
|
||||||
pub mod category;
|
pub mod category;
|
||||||
|
pub mod channel;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod user;
|
mod permission;
|
||||||
|
pub mod permission_sync;
|
||||||
pub mod role;
|
pub mod role;
|
||||||
|
pub mod server;
|
||||||
|
mod server_order;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ServicesContext {
|
pub struct ServicesContext {
|
||||||
@@ -36,6 +38,7 @@ pub struct Services {
|
|||||||
pub message: Arc<MessageService>,
|
pub message: Arc<MessageService>,
|
||||||
pub user: Arc<UserService>,
|
pub user: Arc<UserService>,
|
||||||
pub role: Arc<RoleService>,
|
pub role: Arc<RoleService>,
|
||||||
|
pub permission: Arc<PermissionService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Services {
|
impl Services {
|
||||||
@@ -53,6 +56,7 @@ impl Services {
|
|||||||
let message = Arc::new(MessageService::new(service_context.clone()));
|
let message = Arc::new(MessageService::new(service_context.clone()));
|
||||||
let user = Arc::new(UserService::new(service_context.clone()));
|
let user = Arc::new(UserService::new(service_context.clone()));
|
||||||
let role = Arc::new(RoleService::new(service_context.clone()));
|
let role = Arc::new(RoleService::new(service_context.clone()));
|
||||||
|
let permission = Arc::new(PermissionService::new(service_context.clone()));
|
||||||
|
|
||||||
let services = Self {
|
let services = Self {
|
||||||
permission_sync,
|
permission_sync,
|
||||||
@@ -63,6 +67,7 @@ impl Services {
|
|||||||
message,
|
message,
|
||||||
user,
|
user,
|
||||||
role,
|
role,
|
||||||
|
permission,
|
||||||
};
|
};
|
||||||
let _ = service_context.services.set(services.clone());
|
let _ = service_context.services.set(services.clone());
|
||||||
services
|
services
|
||||||
|
|||||||
@@ -0,0 +1,438 @@
|
|||||||
|
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<ServicesContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionService {
|
||||||
|
pub fn new(service_context: Arc<ServicesContext>) -> 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<C>(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<Uuid> = channels.iter().map(|c| c.id).collect();
|
||||||
|
|
||||||
|
// 2. Charger tous les utilisateurs du serveur
|
||||||
|
let user_ids: Vec<Uuid> = server_user::Entity::find()
|
||||||
|
.filter(server_user::Column::ServerId.eq(server_id))
|
||||||
|
.select_only()
|
||||||
|
.column(server_user::Column::UserId)
|
||||||
|
.into_tuple::<Uuid>()
|
||||||
|
.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<Uuid, Vec<Uuid>> = 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<Uuid, ServerPermission> = 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<Uuid, ServerPermission> = 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<computed_permission::ActiveModel> = 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<C>(
|
||||||
|
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::<Uuid>()
|
||||||
|
.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<Uuid> = 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<Uuid, ChannelPermission> = 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<C>(
|
||||||
|
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::<Uuid>()
|
||||||
|
.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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user