From 9e17a5e73af27b6ed95de7666cf3f67dab5d14ef Mon Sep 17 00:00:00 2001 From: Zomatree Date: Wed, 7 May 2025 00:55:03 +0100 Subject: [PATCH] feat: add parent and update permission calculators --- .../database/src/models/channels/model.rs | 19 +++++++++ crates/core/database/src/util/bridge/v0.rs | 10 +++++ .../database/src/util/bulk_permissions.rs | 40 +++++++++++++++++-- crates/core/database/src/util/permissions.rs | 2 +- crates/core/models/src/v0/channels.rs | 10 +++++ .../delta/src/routes/channels/message_send.rs | 1 + .../delta/src/routes/servers/category_edit.rs | 35 ++++++++++++++-- 7 files changed, 109 insertions(+), 8 deletions(-) diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index 359d7a4a..6359418c 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -100,6 +100,10 @@ auto_derived!( )] role_permissions: HashMap, + /// Category the channel is in + #[serde(skip_serializing_if = "Option::is_none")] + parent: Option, + /// Whether this channel is marked as not safe for work #[serde(skip_serializing_if = "crate::if_false", default)] nsfw: bool, @@ -131,6 +135,10 @@ auto_derived!( )] role_permissions: HashMap, + /// Category the channel is in + #[serde(skip_serializing_if = "Option::is_none")] + parent: Option, + /// Whether this channel is marked as not safe for work #[serde(skip_serializing_if = "crate::if_false", default)] nsfw: bool, @@ -161,6 +169,8 @@ auto_derived!( pub default_permissions: Option, #[serde(skip_serializing_if = "Option::is_none")] pub last_message_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub parent: Option, } /// Optional fields on channel object @@ -219,6 +229,7 @@ impl Channel { default_permissions: None, role_permissions: HashMap::new(), nsfw: data.nsfw.unwrap_or(false), + parent: None }, v0::LegacyServerChannelType::Voice => Channel::VoiceChannel { id: id.clone(), @@ -229,6 +240,7 @@ impl Channel { default_permissions: None, role_permissions: HashMap::new(), nsfw: data.nsfw.unwrap_or(false), + parent: None }, }; @@ -437,6 +449,13 @@ impl Channel { } } + pub fn parent(&self) -> Option<&str> { + match self { + Channel::TextChannel { parent, .. } | Channel::VoiceChannel { parent, .. } => parent.as_deref(), + _ => None + } + } + /// Set role permission on a channel pub async fn set_role_permission( &mut self, diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index 03a800dc..34066a7c 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -187,6 +187,7 @@ impl From for Channel { last_message_id, default_permissions, role_permissions, + parent, nsfw, } => Channel::TextChannel { id, @@ -197,6 +198,7 @@ impl From for Channel { last_message_id, default_permissions, role_permissions, + parent, nsfw, }, crate::Channel::VoiceChannel { @@ -207,6 +209,7 @@ impl From for Channel { icon, default_permissions, role_permissions, + parent, nsfw, } => Channel::VoiceChannel { id, @@ -216,6 +219,7 @@ impl From for Channel { icon: icon.map(|file| file.into()), default_permissions, role_permissions, + parent, nsfw, }, } @@ -267,6 +271,7 @@ impl From for crate::Channel { last_message_id, default_permissions, role_permissions, + parent, nsfw, } => crate::Channel::TextChannel { id, @@ -277,6 +282,7 @@ impl From for crate::Channel { last_message_id, default_permissions, role_permissions, + parent, nsfw, }, Channel::VoiceChannel { @@ -287,6 +293,7 @@ impl From for crate::Channel { icon, default_permissions, role_permissions, + parent, nsfw, } => crate::Channel::VoiceChannel { id, @@ -296,6 +303,7 @@ impl From for crate::Channel { icon: icon.map(|file| file.into()), default_permissions, role_permissions, + parent, nsfw, }, } @@ -315,6 +323,7 @@ impl From for PartialChannel { role_permissions: value.role_permissions, default_permissions: value.default_permissions, last_message_id: value.last_message_id, + parent: value.parent, } } } @@ -332,6 +341,7 @@ impl From for crate::PartialChannel { role_permissions: value.role_permissions, default_permissions: value.default_permissions, last_message_id: value.last_message_id, + parent: value.parent, } } } diff --git a/crates/core/database/src/util/bulk_permissions.rs b/crates/core/database/src/util/bulk_permissions.rs index 928acfce..f3603193 100644 --- a/crates/core/database/src/util/bulk_permissions.rs +++ b/crates/core/database/src/util/bulk_permissions.rs @@ -5,7 +5,7 @@ use revolt_permissions::{ DEFAULT_PERMISSION_DIRECT_MESSAGE, }; -use crate::{Channel, Database, Member, Server, User}; +use crate::{Category, Channel, Database, Member, Server, User}; #[derive(Clone)] pub struct BulkDatabasePermissionQuery<'a> { @@ -197,7 +197,7 @@ async fn calculate_members_permissions<'a>( ) -> HashMap { let mut resp = HashMap::new(); - let (_, channel_role_permissions, channel_default_permissions) = match query + let (_, channel_role_permissions, channel_default_permissions, parent) = match query .channel .as_ref() .expect("A channel must be assigned to calculate channel permissions") @@ -207,14 +207,16 @@ async fn calculate_members_permissions<'a>( id, role_permissions, default_permissions, + parent, .. } | Channel::VoiceChannel { id, role_permissions, default_permissions, + parent, .. - } => (id, role_permissions, default_permissions), + } => (id, role_permissions, default_permissions, parent), _ => panic!("Calculation of member permissions must be done on a server channel"), }; @@ -268,6 +270,8 @@ async fn calculate_members_permissions<'a>( .map(|m| (&m.id.user, m)), ); + let category = parent.map(|parent| query.server.categories.get(&parent).expect("Channel parent doesnt exist")); + for user in users { let member = members.get(&user.id); @@ -298,11 +302,39 @@ async fn calculate_members_permissions<'a>( // Get the user's server permissions let mut permission = calculate_server_permissions(&query.server, user, member); + // Apply category overrides + if let Some(category) = category { + if let Some(defaults) = category.default_permissions { + permission.apply(defaults.into()); + }; + + // Get the applicable role overrides for the category + let mut roles = category.role_permissions + .iter() + .filter(|(id, _)| member.roles.contains(id)) + .filter_map(|(id, permission)| { + query.server.roles.get(id).map(|role| { + let v: Override = (*permission).into(); + (role.rank, v) + }) + }) + .collect::>(); + + roles.sort_by(|a, b| b.0.cmp(&a.0)); + let overrides = roles.into_iter().map(|(_, v)| v); + + for role_override in overrides { + permission.apply(role_override) + } + }; + + // apply channel overrides + if let Some(defaults) = channel_default_permissions { permission.apply(defaults.into()); } - // Get the applicable role overrides + // Get the applicable role overrides for the channel let mut roles = channel_role_permissions .iter() .filter(|(id, _)| member.roles.contains(id)) diff --git a/crates/core/database/src/util/permissions.rs b/crates/core/database/src/util/permissions.rs index 3419b39f..df2d7e06 100644 --- a/crates/core/database/src/util/permissions.rs +++ b/crates/core/database/src/util/permissions.rs @@ -413,7 +413,7 @@ impl PermissionQuery for DatabasePermissionQuery<'_> { async fn set_category_from_channel(&mut self) { if let Some((server, channel)) = self.server.as_ref().zip(self.channel.as_ref()) { - let category = server.categories.values().find(|c| c.channels.contains(&channel.id().to_string())).cloned(); + let category = channel.parent().and_then(|id| server.categories.get(id)).cloned(); self.category = category.map(Cow::Owned); } diff --git a/crates/core/models/src/v0/channels.rs b/crates/core/models/src/v0/channels.rs index 154ca48c..f50461b9 100644 --- a/crates/core/models/src/v0/channels.rs +++ b/crates/core/models/src/v0/channels.rs @@ -101,6 +101,10 @@ auto_derived!( )] role_permissions: HashMap, + /// Category the channel is in + #[serde(skip_serializing_if = "Option::is_none")] + parent: Option, + /// Whether this channel is marked as not safe for work #[cfg_attr( feature = "serde", @@ -138,6 +142,10 @@ auto_derived!( )] role_permissions: HashMap, + /// Category the channel is in + #[serde(skip_serializing_if = "Option::is_none")] + parent: Option, + /// Whether this channel is marked as not safe for work #[cfg_attr( feature = "serde", @@ -170,6 +178,8 @@ auto_derived!( pub default_permissions: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub last_message_id: Option, + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub parent: Option } /// Optional fields on channel object diff --git a/crates/delta/src/routes/channels/message_send.rs b/crates/delta/src/routes/channels/message_send.rs index c44c5b1a..b15aba14 100644 --- a/crates/delta/src/routes/channels/message_send.rs +++ b/crates/delta/src/routes/channels/message_send.rs @@ -192,6 +192,7 @@ mod test { d: ChannelPermission::ViewChannel as i64, }), last_message_id: None, + parent: None, }; locked_channel .update(&harness.db, partial, vec![]) diff --git a/crates/delta/src/routes/servers/category_edit.rs b/crates/delta/src/routes/servers/category_edit.rs index 1af20fb3..e503e290 100644 --- a/crates/delta/src/routes/servers/category_edit.rs +++ b/crates/delta/src/routes/servers/category_edit.rs @@ -1,6 +1,6 @@ use revolt_config::config; use revolt_database::{ - util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Database, PartialCategory, Role, User + util::{permissions::DatabasePermissionQuery, reference::Reference}, Category, Channel, Database, PartialCategory, PartialChannel, Role, User }; use revolt_models::v0::{self, DataEditCategory}; use revolt_permissions::{calculate_server_permissions, ChannelPermission}; @@ -41,16 +41,34 @@ pub async fn edit( let DataEditCategory { title, - channels, + mut channels, remove } = data; + // remove the channels from any existing categories to avoid it having two + for category in server.categories.values_mut() { + category.channels.retain(|c| channels.as_ref().map_or(false, |cs| cs.contains(c))); + } + + // only keep channels which exist in the server + if let Some(channels) = &mut channels { + channels.retain(|c| server.channels.contains(c)); + } + + // unset parent from all channels which are removed from the category + for channel_id in &category.channels { + if channels.as_ref().is_some_and(|cs| !cs.contains(channel_id)) { + db.update_channel(channel_id, &PartialChannel { parent: None, ..Default::default() }, Vec::new()).await?; + }; + }; + + // update the category with the new values category.update( db, &mut server, PartialCategory { title, - channels, + channels: channels.clone(), ..Default::default() }, remove @@ -58,5 +76,16 @@ pub async fn edit( .unwrap_or_default() ).await?; + let channels = db.fetch_channels(&channels.unwrap_or_default()).await?; + + // update all channels to have the parent set + for channel in channels { + if let Channel::TextChannel { ref parent, .. } | Channel::VoiceChannel { ref parent, .. } = channel { + if parent.as_ref() != Some(&category.id) { + db.update_channel(channel.id(), &PartialChannel { parent: Some(category.id.clone()), ..Default::default() }, Vec::new()).await?; + }; + }; + }; + Ok(Json(category.into())) }