diff --git a/crates/core/database/src/models/files/model.rs b/crates/core/database/src/models/files/model.rs index 5ea78f9b..1aaf8b82 100644 --- a/crates/core/database/src/models/files/model.rs +++ b/crates/core/database/src/models/files/model.rs @@ -70,6 +70,7 @@ auto_derived!( LegacyGroupIcon, ChannelIcon, ServerIcon, + RoleIcon, } /// Information about what the file was used for @@ -239,4 +240,23 @@ impl File { ) .await } + + /// Use a file for a role icon + pub async fn use_role_icon( + db: &Database, + id: &str, + parent: &str, + uploader_id: &str, + ) -> Result { + db.find_and_use_attachment( + id, + "icons", + FileUsedFor { + id: parent.to_owned(), + object_type: FileUsedForType::RoleIcon, + }, + uploader_id.to_owned(), + ) + .await + } } diff --git a/crates/core/database/src/models/servers/model.rs b/crates/core/database/src/models/servers/model.rs index 183dd3d2..50c45c59 100644 --- a/crates/core/database/src/models/servers/model.rs +++ b/crates/core/database/src/models/servers/model.rs @@ -86,6 +86,9 @@ auto_derived_partial!( /// Ranking of this role #[serde(default)] pub rank: i64, + /// Custom icon attachment + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, }, "PartialRole" ); @@ -129,6 +132,7 @@ auto_derived!( /// Optional fields on server object pub enum FieldsRole { Colour, + Icon, } ); @@ -305,6 +309,7 @@ impl Role { colour: self.colour, hoist: Some(self.hoist), rank: Some(self.rank), + icon: self.icon, } } @@ -318,6 +323,7 @@ impl Role { colour: None, hoist: false, permissions: Default::default(), + icon: None, }; db.insert_role(&server.id, &role).await?; @@ -367,6 +373,7 @@ impl Role { pub fn remove_field(&mut self, field: &FieldsRole) { match field { FieldsRole::Colour => self.colour = None, + FieldsRole::Icon => self.icon = None, } } diff --git a/crates/core/database/src/models/servers/ops/mongodb.rs b/crates/core/database/src/models/servers/ops/mongodb.rs index d8ef26f2..964de410 100644 --- a/crates/core/database/src/models/servers/ops/mongodb.rs +++ b/crates/core/database/src/models/servers/ops/mongodb.rs @@ -172,6 +172,7 @@ impl IntoDocumentPath for FieldsRole { fn as_path(&self) -> Option<&'static str> { Some(match self { FieldsRole::Colour => "colour", + FieldsRole::Icon => "icon", }) } } diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs index 2e637795..45b80e55 100644 --- a/crates/core/database/src/util/bridge/v0.rs +++ b/crates/core/database/src/util/bridge/v0.rs @@ -190,7 +190,7 @@ impl From for Channel { role_permissions, nsfw, voice, - slowmode + slowmode, } => Channel::TextChannel { id, server, @@ -202,7 +202,7 @@ impl From for Channel { role_permissions, nsfw, voice: voice.map(|voice| voice.into()), - slowmode + slowmode, }, } } @@ -256,7 +256,7 @@ impl From for crate::Channel { role_permissions, nsfw, voice, - slowmode + slowmode, } => crate::Channel::TextChannel { id, server, @@ -268,7 +268,7 @@ impl From for crate::Channel { role_permissions, nsfw, voice: voice.map(|voice| voice.into()), - slowmode + slowmode, }, } } @@ -307,7 +307,7 @@ impl From for crate::PartialChannel { default_permissions: value.default_permissions, last_message_id: value.last_message_id, voice: value.voice.map(|voice| voice.into()), - slowmode: value.slowmode + slowmode: value.slowmode, } } } @@ -926,6 +926,7 @@ impl From for Role { colour: value.colour, hoist: value.hoist, rank: value.rank, + icon: value.icon.map(|f| f.into()), } } } @@ -939,6 +940,7 @@ impl From for crate::Role { colour: value.colour, hoist: value.hoist, rank: value.rank, + icon: value.icon.map(|f| f.into()), } } } @@ -952,6 +954,7 @@ impl From for PartialRole { colour: value.colour, hoist: value.hoist, rank: value.rank, + icon: value.icon.map(|f| f.into()), } } } @@ -965,6 +968,7 @@ impl From for crate::PartialRole { colour: value.colour, hoist: value.hoist, rank: value.rank, + icon: value.icon.map(|f| f.into()), } } } @@ -973,6 +977,7 @@ impl From for FieldsRole { fn from(value: crate::FieldsRole) -> Self { match value { crate::FieldsRole::Colour => FieldsRole::Colour, + crate::FieldsRole::Icon => FieldsRole::Icon, } } } @@ -981,6 +986,7 @@ impl From for crate::FieldsRole { fn from(value: FieldsRole) -> Self { match value { FieldsRole::Colour => crate::FieldsRole::Colour, + FieldsRole::Icon => crate::FieldsRole::Icon, } } } diff --git a/crates/core/models/src/v0/servers.rs b/crates/core/models/src/v0/servers.rs index d9835443..2aab765e 100644 --- a/crates/core/models/src/v0/servers.rs +++ b/crates/core/models/src/v0/servers.rs @@ -106,6 +106,9 @@ auto_derived_partial!( /// Ranking of this role #[cfg_attr(feature = "serde", serde(default))] pub rank: i64, + /// Role icon + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + pub icon: Option, }, "PartialRole" ); @@ -123,6 +126,7 @@ auto_derived!( /// Optional fields on server object pub enum FieldsRole { Colour, + Icon, } /// Channel category @@ -278,6 +282,11 @@ auto_derived!( /// /// **Removed** - no effect, use the edit server role positions route pub rank: Option, + /// Role icon + /// + /// Provide an Autumn attachment Id. + #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))] + pub icon: Option, /// Fields to remove from role object #[cfg_attr(feature = "serde", serde(default))] pub remove: Vec, diff --git a/crates/delta/src/routes/servers/roles_edit.rs b/crates/delta/src/routes/servers/roles_edit.rs index 0a122c1f..e46932ef 100644 --- a/crates/delta/src/routes/servers/roles_edit.rs +++ b/crates/delta/src/routes/servers/roles_edit.rs @@ -1,7 +1,7 @@ use revolt_database::{ util::{permissions::DatabasePermissionQuery, reference::Reference}, voice::{sync_voice_permissions, VoiceClient}, - Database, PartialRole, User + Database, File, PartialRole, User, }; use revolt_models::v0; use revolt_permissions::{calculate_server_permissions, ChannelPermission}; @@ -47,14 +47,27 @@ pub async fn edit( name, colour, hoist, + icon, remove, .. } = data; + if remove.contains(&v0::FieldsRole::Icon) { + if let Some(existing_icon) = &role.icon { + db.mark_attachment_as_deleted(&existing_icon.id).await?; + } + } + + let mut final_icon = None; + if let Some(icon_id) = icon { + final_icon = Some(File::use_role_icon(db, &icon_id, &role_id, &user.id).await?); + } + let partial = PartialRole { name, colour, hoist, + icon: final_icon, ..Default::default() }; @@ -69,8 +82,9 @@ pub async fn edit( for channel_id in &server.channels { let channel = Reference::from_unchecked(channel_id).as_channel(db).await?; - sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)).await?; - }; + sync_voice_permissions(db, voice_client, &channel, Some(&server), Some(&role_id)) + .await?; + } Ok(Json(role.into())) } else {