feat: add role icon support (#724)

Signed-off-by: ispik <ispik@ispik.dev>
This commit is contained in:
İspik
2026-05-03 03:04:28 +03:00
committed by GitHub
parent 279f5d5fd7
commit 841985d3b9
6 changed files with 65 additions and 8 deletions

View File

@@ -70,6 +70,7 @@ auto_derived!(
LegacyGroupIcon, LegacyGroupIcon,
ChannelIcon, ChannelIcon,
ServerIcon, ServerIcon,
RoleIcon,
} }
/// Information about what the file was used for /// Information about what the file was used for
@@ -239,4 +240,23 @@ impl File {
) )
.await .await
} }
/// Use a file for a role icon
pub async fn use_role_icon(
db: &Database,
id: &str,
parent: &str,
uploader_id: &str,
) -> Result<File> {
db.find_and_use_attachment(
id,
"icons",
FileUsedFor {
id: parent.to_owned(),
object_type: FileUsedForType::RoleIcon,
},
uploader_id.to_owned(),
)
.await
}
} }

View File

@@ -86,6 +86,9 @@ auto_derived_partial!(
/// Ranking of this role /// Ranking of this role
#[serde(default)] #[serde(default)]
pub rank: i64, pub rank: i64,
/// Custom icon attachment
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<File>,
}, },
"PartialRole" "PartialRole"
); );
@@ -129,6 +132,7 @@ auto_derived!(
/// Optional fields on server object /// Optional fields on server object
pub enum FieldsRole { pub enum FieldsRole {
Colour, Colour,
Icon,
} }
); );
@@ -305,6 +309,7 @@ impl Role {
colour: self.colour, colour: self.colour,
hoist: Some(self.hoist), hoist: Some(self.hoist),
rank: Some(self.rank), rank: Some(self.rank),
icon: self.icon,
} }
} }
@@ -318,6 +323,7 @@ impl Role {
colour: None, colour: None,
hoist: false, hoist: false,
permissions: Default::default(), permissions: Default::default(),
icon: None,
}; };
db.insert_role(&server.id, &role).await?; db.insert_role(&server.id, &role).await?;
@@ -367,6 +373,7 @@ impl Role {
pub fn remove_field(&mut self, field: &FieldsRole) { pub fn remove_field(&mut self, field: &FieldsRole) {
match field { match field {
FieldsRole::Colour => self.colour = None, FieldsRole::Colour => self.colour = None,
FieldsRole::Icon => self.icon = None,
} }
} }

View File

@@ -172,6 +172,7 @@ impl IntoDocumentPath for FieldsRole {
fn as_path(&self) -> Option<&'static str> { fn as_path(&self) -> Option<&'static str> {
Some(match self { Some(match self {
FieldsRole::Colour => "colour", FieldsRole::Colour => "colour",
FieldsRole::Icon => "icon",
}) })
} }
} }

View File

@@ -190,7 +190,7 @@ impl From<crate::Channel> for Channel {
role_permissions, role_permissions,
nsfw, nsfw,
voice, voice,
slowmode slowmode,
} => Channel::TextChannel { } => Channel::TextChannel {
id, id,
server, server,
@@ -202,7 +202,7 @@ impl From<crate::Channel> for Channel {
role_permissions, role_permissions,
nsfw, nsfw,
voice: voice.map(|voice| voice.into()), voice: voice.map(|voice| voice.into()),
slowmode slowmode,
}, },
} }
} }
@@ -256,7 +256,7 @@ impl From<Channel> for crate::Channel {
role_permissions, role_permissions,
nsfw, nsfw,
voice, voice,
slowmode slowmode,
} => crate::Channel::TextChannel { } => crate::Channel::TextChannel {
id, id,
server, server,
@@ -268,7 +268,7 @@ impl From<Channel> for crate::Channel {
role_permissions, role_permissions,
nsfw, nsfw,
voice: voice.map(|voice| voice.into()), voice: voice.map(|voice| voice.into()),
slowmode slowmode,
}, },
} }
} }
@@ -307,7 +307,7 @@ impl From<PartialChannel> for crate::PartialChannel {
default_permissions: value.default_permissions, default_permissions: value.default_permissions,
last_message_id: value.last_message_id, last_message_id: value.last_message_id,
voice: value.voice.map(|voice| voice.into()), voice: value.voice.map(|voice| voice.into()),
slowmode: value.slowmode slowmode: value.slowmode,
} }
} }
} }
@@ -926,6 +926,7 @@ impl From<crate::Role> for Role {
colour: value.colour, colour: value.colour,
hoist: value.hoist, hoist: value.hoist,
rank: value.rank, rank: value.rank,
icon: value.icon.map(|f| f.into()),
} }
} }
} }
@@ -939,6 +940,7 @@ impl From<Role> for crate::Role {
colour: value.colour, colour: value.colour,
hoist: value.hoist, hoist: value.hoist,
rank: value.rank, rank: value.rank,
icon: value.icon.map(|f| f.into()),
} }
} }
} }
@@ -952,6 +954,7 @@ impl From<crate::PartialRole> for PartialRole {
colour: value.colour, colour: value.colour,
hoist: value.hoist, hoist: value.hoist,
rank: value.rank, rank: value.rank,
icon: value.icon.map(|f| f.into()),
} }
} }
} }
@@ -965,6 +968,7 @@ impl From<PartialRole> for crate::PartialRole {
colour: value.colour, colour: value.colour,
hoist: value.hoist, hoist: value.hoist,
rank: value.rank, rank: value.rank,
icon: value.icon.map(|f| f.into()),
} }
} }
} }
@@ -973,6 +977,7 @@ impl From<crate::FieldsRole> for FieldsRole {
fn from(value: crate::FieldsRole) -> Self { fn from(value: crate::FieldsRole) -> Self {
match value { match value {
crate::FieldsRole::Colour => FieldsRole::Colour, crate::FieldsRole::Colour => FieldsRole::Colour,
crate::FieldsRole::Icon => FieldsRole::Icon,
} }
} }
} }
@@ -981,6 +986,7 @@ impl From<FieldsRole> for crate::FieldsRole {
fn from(value: FieldsRole) -> Self { fn from(value: FieldsRole) -> Self {
match value { match value {
FieldsRole::Colour => crate::FieldsRole::Colour, FieldsRole::Colour => crate::FieldsRole::Colour,
FieldsRole::Icon => crate::FieldsRole::Icon,
} }
} }
} }

View File

@@ -106,6 +106,9 @@ auto_derived_partial!(
/// Ranking of this role /// Ranking of this role
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
pub rank: i64, pub rank: i64,
/// Role icon
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub icon: Option<File>,
}, },
"PartialRole" "PartialRole"
); );
@@ -123,6 +126,7 @@ auto_derived!(
/// Optional fields on server object /// Optional fields on server object
pub enum FieldsRole { pub enum FieldsRole {
Colour, Colour,
Icon,
} }
/// Channel category /// Channel category
@@ -278,6 +282,11 @@ auto_derived!(
/// ///
/// **Removed** - no effect, use the edit server role positions route /// **Removed** - no effect, use the edit server role positions route
pub rank: Option<i64>, pub rank: Option<i64>,
/// Role icon
///
/// Provide an Autumn attachment Id.
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
pub icon: Option<String>,
/// Fields to remove from role object /// Fields to remove from role object
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec<FieldsRole>, pub remove: Vec<FieldsRole>,

View File

@@ -1,7 +1,7 @@
use revolt_database::{ use revolt_database::{
util::{permissions::DatabasePermissionQuery, reference::Reference}, util::{permissions::DatabasePermissionQuery, reference::Reference},
voice::{sync_voice_permissions, VoiceClient}, voice::{sync_voice_permissions, VoiceClient},
Database, PartialRole, User Database, File, PartialRole, User,
}; };
use revolt_models::v0; use revolt_models::v0;
use revolt_permissions::{calculate_server_permissions, ChannelPermission}; use revolt_permissions::{calculate_server_permissions, ChannelPermission};
@@ -47,14 +47,27 @@ pub async fn edit(
name, name,
colour, colour,
hoist, hoist,
icon,
remove, remove,
.. ..
} = data; } = 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 { let partial = PartialRole {
name, name,
colour, colour,
hoist, hoist,
icon: final_icon,
..Default::default() ..Default::default()
}; };
@@ -69,8 +82,9 @@ pub async fn edit(
for channel_id in &server.channels { for channel_id in &server.channels {
let channel = Reference::from_unchecked(channel_id).as_channel(db).await?; 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())) Ok(Json(role.into()))
} else { } else {