diff --git a/crates/core/database/src/events/client.rs b/crates/core/database/src/events/client.rs index af480d5a..2d0829a4 100644 --- a/crates/core/database/src/events/client.rs +++ b/crates/core/database/src/events/client.rs @@ -165,6 +165,9 @@ pub enum EventV1 { /// Server role deleted ServerRoleDelete { id: String, role_id: String }, + /// Server roles ranks updated + ServerRoleRanksUpdate { ranks: Vec }, + /// Update existing user UserUpdate { id: String, diff --git a/crates/core/models/src/v0/servers.rs b/crates/core/models/src/v0/servers.rs index ae5a50a2..9e181176 100644 --- a/crates/core/models/src/v0/servers.rs +++ b/crates/core/models/src/v0/servers.rs @@ -267,6 +267,7 @@ auto_derived!( pub hoist: Option, /// Ranking position /// + /// **Deprecated** - use the edit server role positions route. /// Smaller values take priority. pub rank: Option, /// Fields to remove from role object @@ -286,4 +287,9 @@ auto_derived!( /// Whether to not send a leave message pub leave_silently: Option, } + + /// New role positions + pub struct DataEditRoleRanks { + pub ranks: Vec, + } ); diff --git a/crates/delta/src/routes/servers/mod.rs b/crates/delta/src/routes/servers/mod.rs index a7553625..b656b401 100644 --- a/crates/delta/src/routes/servers/mod.rs +++ b/crates/delta/src/routes/servers/mod.rs @@ -18,6 +18,7 @@ mod roles_create; mod roles_delete; mod roles_edit; mod roles_fetch; +mod roles_edit_positions; mod server_ack; mod server_create; mod server_delete; @@ -47,6 +48,7 @@ pub fn routes() -> (Vec, OpenApi) { roles_delete::delete, permissions_set::set_role_permission, permissions_set_default::set_default_permissions, - emoji_list::list_emoji + emoji_list::list_emoji, + roles_edit_positions::edit_role_positions ] } diff --git a/crates/delta/src/routes/servers/roles_edit.rs b/crates/delta/src/routes/servers/roles_edit.rs index 72ec6635..af4e0cac 100644 --- a/crates/delta/src/routes/servers/roles_edit.rs +++ b/crates/delta/src/routes/servers/roles_edit.rs @@ -45,22 +45,15 @@ pub async fn edit( name, colour, hoist, - rank, remove, + // rank is deprecated + .. } = data; - // Prevent us from moving a role above other roles - if let Some(rank) = &rank { - if rank <= &member_rank { - return Err(create_error!(NotElevated)); - } - } - let partial = PartialRole { name, colour, hoist, - rank, ..Default::default() }; diff --git a/crates/delta/src/routes/servers/roles_edit_positions.rs b/crates/delta/src/routes/servers/roles_edit_positions.rs new file mode 100644 index 00000000..5a7e4438 --- /dev/null +++ b/crates/delta/src/routes/servers/roles_edit_positions.rs @@ -0,0 +1,80 @@ +use revolt_database::{events::client::EventV1, util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, PartialRole, User}; +use revolt_permissions::{calculate_server_permissions, ChannelPermission}; +use rocket::{serde::json::Json, State}; +use revolt_result::{create_error, Result}; +use revolt_models::v0; + +/// # Edits server roles ranks +/// +/// Edit's server role's ranks. +#[openapi(tag = "Server Permissions")] +#[patch("//roles/ranks", data = "")] +pub async fn edit_role_ranks( + db: &State, + user: User, + target: Reference, + data: Json, +) -> Result> { + let data = data.into_inner(); + + let mut server = target.as_server(db).await?; + let mut query = DatabasePermissionQuery::new(db, &user).server(&server); + calculate_server_permissions(&mut query) + .await + .throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?; + + let existing_order = server.roles.keys().cloned().collect::>(); + let new_order = data.ranks.clone().into_iter().collect::>(); + + // verify all roles are in the new ordering + if data.ranks.len() != server.roles.len() && server.roles.iter().all(|(id, _)| data.ranks.contains(id)) { + return Err(create_error!(InvalidOperation)) + } + + let member_top_rank = query.get_member_rank(); + + // find all roles above the member which we should not be able to reorder + let cant_modify = server.roles.clone() + .into_iter() + .filter(|(_, role)| if let Some(top_rank) = member_top_rank { role.rank >= top_rank } else { true }) + .collect::>(); + + // check if any roles which we cant reorder have tried to been reordered + if cant_modify.iter() + .any(|(id, _)| { + let existing_rank = existing_order.iter().position(|existing_id| id == existing_id); + let new_rank = new_order.iter().position(|new_id| id == new_id); + + existing_rank != new_rank + }) + { + return Err(create_error!(NotElevated)) + } + + // update the roles which have had their rank changed + for (role_id, role) in &mut server.roles { + let new_rank = new_order.iter().position(|id| id == role_id).unwrap() as i64; // unwrap - we check if its in the data earlier on + + if new_rank != role.rank { + // cant use Role::update here because we dont want to publish seperate role update events + let partial = PartialRole { + rank: Some(new_rank), + ..Default::default() + }; + + db.update_role(&server.id, role_id, &partial, Vec::new()).await?; + + role.apply_options(PartialRole { + rank: Some(new_rank), + ..Default::default() + }); + } + } + + // publish bulk update event + EventV1::ServerRoleRanksUpdate { + ranks: new_order + }.p(server.id.clone()).await; + + Ok(Json(server.into())) +}