feat: add parent and update permission calculators

This commit is contained in:
Zomatree
2025-05-07 00:55:03 +01:00
parent c7f09ab9d0
commit 9e17a5e73a
7 changed files with 109 additions and 8 deletions

View File

@@ -100,6 +100,10 @@ auto_derived!(
)] )]
role_permissions: HashMap<String, OverrideField>, role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work /// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)] #[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool, nsfw: bool,
@@ -131,6 +135,10 @@ auto_derived!(
)] )]
role_permissions: HashMap<String, OverrideField>, role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work /// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)] #[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool, nsfw: bool,
@@ -161,6 +169,8 @@ auto_derived!(
pub default_permissions: Option<OverrideField>, pub default_permissions: Option<OverrideField>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub last_message_id: Option<String>, pub last_message_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
} }
/// Optional fields on channel object /// Optional fields on channel object
@@ -219,6 +229,7 @@ impl Channel {
default_permissions: None, default_permissions: None,
role_permissions: HashMap::new(), role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false), nsfw: data.nsfw.unwrap_or(false),
parent: None
}, },
v0::LegacyServerChannelType::Voice => Channel::VoiceChannel { v0::LegacyServerChannelType::Voice => Channel::VoiceChannel {
id: id.clone(), id: id.clone(),
@@ -229,6 +240,7 @@ impl Channel {
default_permissions: None, default_permissions: None,
role_permissions: HashMap::new(), role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false), 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 /// Set role permission on a channel
pub async fn set_role_permission( pub async fn set_role_permission(
&mut self, &mut self,

View File

@@ -187,6 +187,7 @@ impl From<crate::Channel> for Channel {
last_message_id, last_message_id,
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
} => Channel::TextChannel { } => Channel::TextChannel {
id, id,
@@ -197,6 +198,7 @@ impl From<crate::Channel> for Channel {
last_message_id, last_message_id,
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
}, },
crate::Channel::VoiceChannel { crate::Channel::VoiceChannel {
@@ -207,6 +209,7 @@ impl From<crate::Channel> for Channel {
icon, icon,
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
} => Channel::VoiceChannel { } => Channel::VoiceChannel {
id, id,
@@ -216,6 +219,7 @@ impl From<crate::Channel> for Channel {
icon: icon.map(|file| file.into()), icon: icon.map(|file| file.into()),
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
}, },
} }
@@ -267,6 +271,7 @@ impl From<Channel> for crate::Channel {
last_message_id, last_message_id,
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
} => crate::Channel::TextChannel { } => crate::Channel::TextChannel {
id, id,
@@ -277,6 +282,7 @@ impl From<Channel> for crate::Channel {
last_message_id, last_message_id,
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
}, },
Channel::VoiceChannel { Channel::VoiceChannel {
@@ -287,6 +293,7 @@ impl From<Channel> for crate::Channel {
icon, icon,
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
} => crate::Channel::VoiceChannel { } => crate::Channel::VoiceChannel {
id, id,
@@ -296,6 +303,7 @@ impl From<Channel> for crate::Channel {
icon: icon.map(|file| file.into()), icon: icon.map(|file| file.into()),
default_permissions, default_permissions,
role_permissions, role_permissions,
parent,
nsfw, nsfw,
}, },
} }
@@ -315,6 +323,7 @@ impl From<crate::PartialChannel> for PartialChannel {
role_permissions: value.role_permissions, role_permissions: value.role_permissions,
default_permissions: value.default_permissions, default_permissions: value.default_permissions,
last_message_id: value.last_message_id, last_message_id: value.last_message_id,
parent: value.parent,
} }
} }
} }
@@ -332,6 +341,7 @@ impl From<PartialChannel> for crate::PartialChannel {
role_permissions: value.role_permissions, role_permissions: value.role_permissions,
default_permissions: value.default_permissions, default_permissions: value.default_permissions,
last_message_id: value.last_message_id, last_message_id: value.last_message_id,
parent: value.parent,
} }
} }
} }

View File

@@ -5,7 +5,7 @@ use revolt_permissions::{
DEFAULT_PERMISSION_DIRECT_MESSAGE, DEFAULT_PERMISSION_DIRECT_MESSAGE,
}; };
use crate::{Channel, Database, Member, Server, User}; use crate::{Category, Channel, Database, Member, Server, User};
#[derive(Clone)] #[derive(Clone)]
pub struct BulkDatabasePermissionQuery<'a> { pub struct BulkDatabasePermissionQuery<'a> {
@@ -197,7 +197,7 @@ async fn calculate_members_permissions<'a>(
) -> HashMap<String, PermissionValue> { ) -> HashMap<String, PermissionValue> {
let mut resp = HashMap::new(); 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 .channel
.as_ref() .as_ref()
.expect("A channel must be assigned to calculate channel permissions") .expect("A channel must be assigned to calculate channel permissions")
@@ -207,14 +207,16 @@ async fn calculate_members_permissions<'a>(
id, id,
role_permissions, role_permissions,
default_permissions, default_permissions,
parent,
.. ..
} }
| Channel::VoiceChannel { | Channel::VoiceChannel {
id, id,
role_permissions, role_permissions,
default_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"), _ => 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)), .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 { for user in users {
let member = members.get(&user.id); let member = members.get(&user.id);
@@ -298,11 +302,39 @@ async fn calculate_members_permissions<'a>(
// Get the user's server permissions // Get the user's server permissions
let mut permission = calculate_server_permissions(&query.server, user, member); 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::<Vec<(i64, Override)>>();
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 { if let Some(defaults) = channel_default_permissions {
permission.apply(defaults.into()); permission.apply(defaults.into());
} }
// Get the applicable role overrides // Get the applicable role overrides for the channel
let mut roles = channel_role_permissions let mut roles = channel_role_permissions
.iter() .iter()
.filter(|(id, _)| member.roles.contains(id)) .filter(|(id, _)| member.roles.contains(id))

View File

@@ -413,7 +413,7 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
async fn set_category_from_channel(&mut self) { async fn set_category_from_channel(&mut self) {
if let Some((server, channel)) = self.server.as_ref().zip(self.channel.as_ref()) { 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); self.category = category.map(Cow::Owned);
} }

View File

@@ -101,6 +101,10 @@ auto_derived!(
)] )]
role_permissions: HashMap<String, OverrideField>, role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work /// Whether this channel is marked as not safe for work
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
@@ -138,6 +142,10 @@ auto_derived!(
)] )]
role_permissions: HashMap<String, OverrideField>, role_permissions: HashMap<String, OverrideField>,
/// Category the channel is in
#[serde(skip_serializing_if = "Option::is_none")]
parent: Option<String>,
/// Whether this channel is marked as not safe for work /// Whether this channel is marked as not safe for work
#[cfg_attr( #[cfg_attr(
feature = "serde", feature = "serde",
@@ -170,6 +178,8 @@ auto_derived!(
pub default_permissions: Option<OverrideField>, pub default_permissions: Option<OverrideField>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub last_message_id: Option<String>, pub last_message_id: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub parent: Option<String>
} }
/// Optional fields on channel object /// Optional fields on channel object

View File

@@ -192,6 +192,7 @@ mod test {
d: ChannelPermission::ViewChannel as i64, d: ChannelPermission::ViewChannel as i64,
}), }),
last_message_id: None, last_message_id: None,
parent: None,
}; };
locked_channel locked_channel
.update(&harness.db, partial, vec![]) .update(&harness.db, partial, vec![])

View File

@@ -1,6 +1,6 @@
use revolt_config::config; use revolt_config::config;
use revolt_database::{ 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_models::v0::{self, DataEditCategory};
use revolt_permissions::{calculate_server_permissions, ChannelPermission}; use revolt_permissions::{calculate_server_permissions, ChannelPermission};
@@ -41,16 +41,34 @@ pub async fn edit(
let DataEditCategory { let DataEditCategory {
title, title,
channels, mut channels,
remove remove
} = data; } = 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( category.update(
db, db,
&mut server, &mut server,
PartialCategory { PartialCategory {
title, title,
channels, channels: channels.clone(),
..Default::default() ..Default::default()
}, },
remove remove
@@ -58,5 +76,16 @@ pub async fn edit(
.unwrap_or_default() .unwrap_or_default()
).await?; ).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())) Ok(Json(category.into()))
} }