Skip to content

category permissions #419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/core/config/Revolt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ message_reactions = 20
server_emoji = 100
server_roles = 200
server_channels = 200
server_categories = 50

# How many hours since creation a user is considered new
new_user_hours = 72
Expand Down
1 change: 1 addition & 0 deletions crates/core/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ pub struct GlobalLimits {
pub server_emoji: usize,
pub server_roles: usize,
pub server_channels: usize,
pub server_categories: usize,

pub new_user_hours: usize,

Expand Down
19 changes: 19 additions & 0 deletions crates/core/database/src/models/channels/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ auto_derived!(
)]
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
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
Expand Down Expand Up @@ -131,6 +135,10 @@ auto_derived!(
)]
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
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
Expand Down Expand Up @@ -161,6 +169,8 @@ auto_derived!(
pub default_permissions: Option<OverrideField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_message_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent: Option<String>,
}

/// Optional fields on channel object
Expand Down Expand Up @@ -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(),
Expand All @@ -229,6 +240,7 @@ impl Channel {
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
parent: None
},
};

Expand Down Expand Up @@ -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,
Expand Down
134 changes: 127 additions & 7 deletions crates/core/database/src/models/servers/model.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet};

use revolt_models::v0::{self, DataCreateServerChannel};
use revolt_models::v0::{self, DataCreateCategory, DataCreateServerChannel, DataEditCategory};
use revolt_permissions::{OverrideField, DEFAULT_PERMISSION_SERVER};
use revolt_result::Result;
use ulid::Ulid;
Expand All @@ -26,8 +26,8 @@ auto_derived_partial!(
// TODO: investigate if this is redundant and can be removed
pub channels: Vec<String>,
/// Categories for this server
#[serde(skip_serializing_if = "Option::is_none")]
pub categories: Option<Vec<Category>>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub categories: HashMap<String, Category>,
/// Configuration for sending system event messages
#[serde(skip_serializing_if = "Option::is_none")]
pub system_messages: Option<SystemMessageChannels>,
Expand Down Expand Up @@ -87,17 +87,29 @@ auto_derived_partial!(
"PartialRole"
);

auto_derived!(
auto_derived_partial!(
/// Channel category
pub struct Category {
/// Unique ID for this category
pub id: String,
/// Title for this category
pub title: String,
/// Default permissions assigned to users in this category
#[serde(skip_serializing_if = "Option::is_none")]
pub default_permissions: Option<OverrideField>,
/// Permissions assigned based on role to this category
#[serde(
default = "HashMap::<String, OverrideField>::new",
skip_serializing_if = "HashMap::<String, OverrideField>::is_empty"
)]
pub role_permissions: HashMap<String, OverrideField>,
/// Channels in this category
pub channels: Vec<String>,
}
},
"PartialCategory"
);

auto_derived!(
/// System message channel assignments
pub struct SystemMessageChannels {
/// ID of channel to send user join messages in
Expand Down Expand Up @@ -127,6 +139,10 @@ auto_derived!(
pub enum FieldsRole {
Colour,
}

pub enum FieldsCategory {
DefaultPermissions
}
);

#[allow(clippy::disallowed_methods)]
Expand All @@ -149,7 +165,7 @@ impl Server {

analytics: false,
banner: None,
categories: None,
categories: HashMap::new(),
discoverable: false,
flags: None,
icon: None,
Expand Down Expand Up @@ -221,7 +237,7 @@ impl Server {
pub fn remove_field(&mut self, field: &FieldsServer) {
match field {
FieldsServer::Description => self.description = None,
FieldsServer::Categories => self.categories = None,
FieldsServer::Categories => self.categories.clear(),
FieldsServer::SystemMessages => self.system_messages = None,
FieldsServer::Icon => self.icon = None,
FieldsServer::Banner => self.banner = None,
Expand Down Expand Up @@ -334,6 +350,110 @@ impl Role {
}
}

impl Category {
pub fn remove_field(&mut self, field: FieldsCategory) {
match field {
FieldsCategory::DefaultPermissions => self.default_permissions = None
};
}

pub async fn create(db: &Database, server: &mut Server, data: DataCreateCategory) -> Result<Category> {
let channels = data.channels.clone().unwrap_or_default()
.into_iter()
.filter(|c| server.channels.contains(&c))
.collect();

let category = Category {
id: Ulid::new().to_string(),
title: data.title,
channels: channels,
default_permissions: None,
role_permissions: HashMap::new()
};

server.categories.insert(category.id.clone(), category.clone());

let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };

db.update_server(&server.id, &partial_server, Vec::new()).await?;

EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;

Ok(category)
}

pub async fn delete(&self, db: &Database, server: &mut Server) -> Result<()> {
// update the parent server model with the new category
server.categories.remove(&self.id);

let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };

db.update_server(&server.id, &partial_server, Vec::new()).await?;

EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;

Ok(())
}

pub async fn update(&mut self, db: &Database, server: &mut Server, partial: PartialCategory, remove: Vec<FieldsCategory>) -> Result<()> {
for field in remove {
self.remove_field(field);
};

self.apply_options(partial);

// update the parent server model with the new category
server.categories.insert(self.id.clone(), self.clone());

let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };

db.update_server(&server.id, &partial_server, Vec::new()).await?;

EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;

Ok(())
}

pub async fn set_role_permission(&mut self, db: &Database, server: &mut Server, role_id: String, role_override: OverrideField) -> Result<()> {
self.role_permissions.insert(role_id, role_override);

// update the parent server model with the new category
server.categories.insert(self.id.clone(), self.clone());

let partial_server = PartialServer { categories: Some(server.categories.clone()), ..Default::default() };

db.update_server(&server.id, &partial_server, Vec::new()).await?;

EventV1::ServerUpdate {
id: server.id.clone(),
data: partial_server.into(),
clear: Vec::new(),
}
.p(server.id.clone())
.await;

Ok(())
}
}

impl SystemMessageChannels {
pub fn into_channel_ids(self) -> HashSet<String> {
let mut ids = HashSet::new();
Expand Down
Loading
Loading