From 6070ad8e0a92332cb2a433a24d5f0300410c4288 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 15:16:45 +0200 Subject: [PATCH 01/13] Implement GuildMember::get() --- src/api/guilds/member.rs | 54 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/api/guilds/member.rs b/src/api/guilds/member.rs index b301b99..a6ec7d7 100644 --- a/src/api/guilds/member.rs +++ b/src/api/guilds/member.rs @@ -1,8 +1,60 @@ use reqwest::Client; +use serde_json::from_str; -use crate::{instance::UserMeta, limit::LimitedRequester, types}; +use crate::{errors::ChorusLibError, instance::UserMeta, limit::LimitedRequester, types}; impl types::GuildMember { + /// Retrieves a guild member by their ID. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// * `guild_id` - The ID of the guild. + /// * `member_id` - The ID of the member. + /// + /// # Returns + /// + /// A [`Result`] containing a [`GuildMember`] if the request succeeds, or a [`ChorusLibError`] if the request fails. + pub async fn get( + user: &mut UserMeta, + guild_id: &str, + member_id: &str, + ) -> Result { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/guilds/{}/members/{}/", + belongs_to.urls.get_api(), + guild_id, + member_id + ); + let request = Client::new().get(url).bearer_auth(user.token()); + let response = LimitedRequester::new() + .await + .send_request( + request, + crate::api::limits::LimitType::Guild, + &mut belongs_to.limits, + &mut user.limits, + ) + .await + .unwrap(); + let response_text = match response.text().await { + Ok(string) => string, + Err(e) => { + return Err(ChorusLibError::InvalidResponseError { + error: e.to_string(), + }) + } + }; + let member = from_str::(&response_text); + if member.is_err() { + return Err(ChorusLibError::InvalidResponseError { + error: member.err().unwrap().to_string(), + }); + } + Ok(member.unwrap()) + } + /// Adds a role to a guild member. /// /// # Arguments From 70df27162f74a967aa1130d36cc950c539dea4c8 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 17:35:09 +0200 Subject: [PATCH 02/13] Complete this test --- tests/member.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/member.rs b/tests/member.rs index f4b22fd..a19f5e0 100644 --- a/tests/member.rs +++ b/tests/member.rs @@ -7,7 +7,32 @@ async fn add_remove_role() { let role_id = &bundle.role.id.to_string(); let user_id = &bundle.user.object.id.to_string(); chorus::types::GuildMember::add_role(&mut bundle.user, guild_id, user_id, role_id).await; + let member = chorus::types::GuildMember::get(&mut bundle.user, &guild_id, &user_id) + .await + .unwrap(); + let mut role_found = false; + for role in member.roles.iter() { + if role == role_id { + println!("Role found: {:?}", role); + role_found = true; + } + } + if !role_found { + assert!(false) + } chorus::types::GuildMember::remove_role(&mut bundle.user, guild_id, user_id, role_id).await; - // TODO: Implement /guilds/{guild_id}/members/{member_id}/ GET route. + let member = chorus::types::GuildMember::get(&mut bundle.user, &guild_id, &user_id) + .await + .unwrap(); + for role in member.roles.iter() { + if role != role_id { + role_found = false; + } else { + assert!(false); + } + } + if role_found { + assert!(false) + } common::teardown(bundle).await } From 2652ae68e0344ee1b370386751399cd5a64297e7 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 18:42:37 +0200 Subject: [PATCH 03/13] Add RoleObject::update() --- src/api/guilds/roles.rs | 65 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index a65b36d..9ebbb9c 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -161,4 +161,69 @@ impl types::RoleObject { }; Ok(role) } + + /// Updates a role in a guild. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// * `guild_id` - The ID of the guild to update the role in. + /// * `role_id` - The ID of the role to update. + /// * `role_create_schema` - A [`RoleCreateModifySchema`] instance containing the new properties of the role. + /// + /// # Returns + /// + /// A `Result` containing the updated [`RoleObject`] if successful, or a [`ChorusLibError`] if the request fails or if the response is invalid. + /// + /// # Errors + /// + /// Returns a [`ChorusLibError`] if the request fails or if the response is invalid. + pub async fn update( + user: &mut UserMeta, + guild_id: &str, + role_id: &str, + role_create_schema: RoleCreateModifySchema, + ) -> Result { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/guilds/{}/roles/{}", + belongs_to.urls.get_api(), + guild_id, + role_id + ); + let body = match to_string::(&role_create_schema) { + Ok(string) => string, + Err(e) => { + return Err(ChorusLibError::FormCreationError { + error: e.to_string(), + }) + } + }; + let request = Client::new() + .patch(url) + .bearer_auth(user.token()) + .body(body); + let result = match LimitedRequester::new() + .await + .send_request( + request, + crate::api::limits::LimitType::Guild, + &mut belongs_to.limits, + &mut user.limits, + ) + .await + { + Ok(request) => request, + Err(e) => return Err(e), + }; + let role: RoleObject = match from_str(&result.text().await.unwrap()) { + Ok(role) => role, + Err(e) => { + return Err(ChorusLibError::InvalidResponseError { + error: e.to_string(), + }) + } + }; + Ok(role) + } } From f0e39334191c853f36dbf56e8744b8b6994db003 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 18:48:37 +0200 Subject: [PATCH 04/13] Implement RoleObject::get() --- src/api/guilds/roles.rs | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index 9ebbb9c..49a35b3 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -52,6 +52,52 @@ impl types::RoleObject { Ok(Some(roles)) } + /// Retrieves a single role for a given guild. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// * `guild_id` - The ID of the guild to retrieve the role from. + /// * `role_id` - The ID of the role to retrieve. + /// + /// # Returns + /// + /// A `Result` containing the retrieved [`RoleObject`] if successful, or a [`ChorusLibError`] if the request fails or if the response is invalid. + /// + /// # Errors + /// + /// Returns a [`ChorusLibError`] if the request fails or if the response is invalid. + pub async fn get( + user: &mut UserMeta, + guild_id: &str, + role_id: &str, + ) -> Result { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/guilds/{}/roles/{}/", + belongs_to.urls.get_api(), + guild_id, + role_id + ); + let request = Client::new().get(url).bearer_auth(user.token()); + let requester = match LimitedRequester::new() + .await + .send_request( + request, + crate::api::limits::LimitType::Guild, + &mut belongs_to.limits, + &mut user.limits, + ) + .await + { + Ok(request) => request, + Err(e) => return Err(e), + }; + let role: RoleObject = from_str(&requester.text().await.unwrap()).unwrap(); + + Ok(role) + } + /// Creates a new role for a given guild. /// /// # Arguments From f5fba7c34af4d9094ec783d3ebcef6665a1a0362 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 18:50:49 +0200 Subject: [PATCH 05/13] Better error handling in get methods. --- src/api/guilds/roles.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index 49a35b3..b2e9b31 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -30,7 +30,7 @@ impl types::RoleObject { let mut belongs_to = user.belongs_to.borrow_mut(); let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id); let request = Client::new().get(url).bearer_auth(user.token()); - let requester = match LimitedRequester::new() + let result = match LimitedRequester::new() .await .send_request( request, @@ -43,7 +43,14 @@ impl types::RoleObject { Ok(request) => request, Err(e) => return Err(e), }; - let roles: Vec = from_str(&requester.text().await.unwrap()).unwrap(); + let roles: Vec = match from_str(&result.text().await.unwrap()) { + Ok(roles) => roles, + Err(e) => { + return Err(ChorusLibError::InvalidResponseError { + error: e.to_string(), + }) + } + }; if roles.is_empty() { return Ok(None); @@ -80,7 +87,7 @@ impl types::RoleObject { role_id ); let request = Client::new().get(url).bearer_auth(user.token()); - let requester = match LimitedRequester::new() + let result = match LimitedRequester::new() .await .send_request( request, @@ -93,7 +100,14 @@ impl types::RoleObject { Ok(request) => request, Err(e) => return Err(e), }; - let role: RoleObject = from_str(&requester.text().await.unwrap()).unwrap(); + let role: RoleObject = match from_str(&result.text().await.unwrap()) { + Ok(role) => role, + Err(e) => { + return Err(ChorusLibError::InvalidResponseError { + error: e.to_string(), + }) + } + }; Ok(role) } From 65a73ec85b4ee6bb69498ab1885a9739aa8d6b93 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 19:17:35 +0200 Subject: [PATCH 06/13] Mark role management as completed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0ab1f4..10cefd9 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ - [ ] [Video chat support](https://github.com/polyphony-chat/chorus/issues/49) ### Permissions and Roles -- [ ] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification) +- [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification) - [ ] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions) - [ ] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88) - [ ] Role-based access control From 7d321798ee0f4a083c71edb6be59c50ffcbc4c50 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 19:35:57 +0200 Subject: [PATCH 07/13] "Normalize" RolePositionUpdateSchema --- src/types/schema/role.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/schema/role.rs b/src/types/schema/role.rs index 97ab248..65faa39 100644 --- a/src/types/schema/role.rs +++ b/src/types/schema/role.rs @@ -21,6 +21,6 @@ pub struct RoleCreateModifySchema { /// Represents the schema which needs to be sent to update a roles' position. /// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolepositionupdateschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolepositionupdateschema) pub struct RolePositionUpdateSchema { - pub id: Snowflake, - pub position: i32, + pub id: String, + pub position: u16, } From 7c358c338f977e75d1b1abef07b578d2c2f12eb4 Mon Sep 17 00:00:00 2001 From: Flori Weber Date: Sat, 10 Jun 2023 19:42:41 +0200 Subject: [PATCH 08/13] Test singular role --- tests/roles.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/roles.rs b/tests/roles.rs index 3eee2b8..2663d25 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -34,3 +34,16 @@ async fn create_and_get_roles() { assert_eq!(role, expected); common::teardown(bundle).await } + +#[tokio::test] +async fn get_singular_role() { + let mut bundle = common::setup().await; + let guild_id = &bundle.guild.id.to_string(); + let role_id = &bundle.role.id.to_string(); + let role = bundle.role.clone(); + let same_role = chorus::types::RoleObject::get(&mut bundle.user, guild_id, role_id) + .await + .unwrap(); + assert_eq!(role, same_role); + common::teardown(bundle).await +} From 6b1dd903555c13201de04f56f3c22d5a5cded624 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 10 Jun 2023 21:51:45 +0200 Subject: [PATCH 09/13] Create permissions.rs --- src/api/channels/mod.rs | 2 ++ src/api/channels/permissions.rs | 0 2 files changed, 2 insertions(+) create mode 100644 src/api/channels/permissions.rs diff --git a/src/api/channels/mod.rs b/src/api/channels/mod.rs index 28a9dd3..94b42b6 100644 --- a/src/api/channels/mod.rs +++ b/src/api/channels/mod.rs @@ -1,7 +1,9 @@ pub mod channels; pub mod messages; +pub mod permissions; pub mod reactions; pub use channels::*; pub use messages::*; +pub use permissions::*; pub use reactions::*; diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs new file mode 100644 index 0000000..e69de29 From b20fc61aea2176d40955eca951ab4b5803a0fb71 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 10 Jun 2023 22:09:53 +0200 Subject: [PATCH 10/13] impl Channel::edit_permissions() --- src/api/channels/permissions.rs | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs index e69de29..8754638 100644 --- a/src/api/channels/permissions.rs +++ b/src/api/channels/permissions.rs @@ -0,0 +1,56 @@ +use reqwest::Client; +use serde_json::to_string; + +use crate::{ + errors::ChorusLibError, + instance::UserMeta, + limit::LimitedRequester, + types::{self, PermissionOverwrite}, +}; + +impl types::Channel { + /// Edits the permission overwrites for a channel. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// * `channel_id` - A string slice representing the ID of the channel. + /// * `overwrite` - A [`PermissionOverwrite`] instance representing the new permission overwrites. + /// + /// # Returns + /// + /// This function returns [`None`] if the request is successful, otherwise it returns a [`ChorusLibError`] instance. + pub async fn edit_permissions( + user: &mut UserMeta, + channel_id: &str, + overwrite: PermissionOverwrite, + ) -> Option { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/channels/{}/permissions/{}", + belongs_to.urls.get_api(), + channel_id, + overwrite.id + ); + let body = match to_string(&overwrite) { + Ok(string) => string, + Err(e) => { + return Some(ChorusLibError::FormCreationError { + error: e.to_string(), + }); + } + }; + let request = Client::new().put(url).bearer_auth(user.token()).body(body); + LimitedRequester::new() + .await + .send_request( + request, + crate::api::limits::LimitType::Channel, + &mut belongs_to.limits, + &mut user.limits, + ) + .await + .unwrap(); + None + } +} From 9940394f201acaff10de8d23cfbf3beea4ffa460 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 10 Jun 2023 22:16:53 +0200 Subject: [PATCH 11/13] Implement delete_permission --- src/api/channels/permissions.rs | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/api/channels/permissions.rs b/src/api/channels/permissions.rs index 8754638..0005d15 100644 --- a/src/api/channels/permissions.rs +++ b/src/api/channels/permissions.rs @@ -53,4 +53,43 @@ impl types::Channel { .unwrap(); None } + + /// Deletes a permission overwrite for a channel. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// * `channel_id` - A string slice representing the ID of the channel. + /// * `overwrite_id` - A string slice representing the ID of the permission overwrite to delete. + /// + /// # Returns + /// + /// This function returns [`None`] if the request is successful, otherwise it returns a [`ChorusLibError`] instance. + pub async fn delete_permission( + user: &mut UserMeta, + channel_id: &str, + overwrite_id: &str, + ) -> Option { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/channels/{}/permissions/{}", + belongs_to.urls.get_api(), + channel_id, + overwrite_id + ); + let request = Client::new().delete(url).bearer_auth(user.token()); + let response = LimitedRequester::new() + .await + .send_request( + request, + crate::api::limits::LimitType::Channel, + &mut belongs_to.limits, + &mut user.limits, + ) + .await; + if response.is_err() { + return Some(response.err().unwrap()); + } + None + } } From c9e6c63073eaa306915d97e615eb50acbfd63d60 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 10 Jun 2023 22:26:15 +0200 Subject: [PATCH 12/13] Add tests for permission modification --- tests/channel.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/channel.rs b/tests/channel.rs index 85b019b..8ae8e58 100644 --- a/tests/channel.rs +++ b/tests/channel.rs @@ -1,5 +1,5 @@ mod common; -use chorus::types::{self, Channel}; +use chorus::types::{self, Channel, PermissionFlags, PermissionOverwrite}; #[tokio::test] async fn get_channel() { @@ -54,5 +54,31 @@ async fn modify_channel() { .await .unwrap(); assert_eq!(result.name, Some("beepboop".to_string())); + + let permission_override = PermissionFlags::from_vec(Vec::from([ + PermissionFlags::MANAGE_CHANNELS, + PermissionFlags::MANAGE_MESSAGES, + ])); + let permission_override = PermissionOverwrite { + id: bundle.user.object.id.to_string(), + overwrite_type: "1".to_string(), + allow: permission_override, + deny: "0".to_string(), + }; + + Channel::edit_permissions( + &mut bundle.user, + bundle.channel.id.to_string().as_str(), + permission_override.clone(), + ) + .await; + + Channel::delete_permission( + &mut bundle.user, + bundle.channel.id.to_string().as_str(), + &permission_override.id, + ) + .await; + common::teardown(bundle).await } From aff744d96063af1705bf4660d457d0e347c4c9fa Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sat, 10 Jun 2023 22:27:46 +0200 Subject: [PATCH 13/13] Mark 2 objects as complete --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10cefd9..ebb1c26 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ ### Permissions and Roles - [x] [Role management](https://github.com/polyphony-chat/chorus/issues/46) (creation, deletion, modification) - [ ] [Permission management](https://github.com/polyphony-chat/chorus/issues/46) (assigning and revoking permissions) -- [ ] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88) -- [ ] Role-based access control +- [x] [Channel-specific permissions](https://github.com/polyphony-chat/chorus/issues/88) +- [x] Role-based access control ### Guild Management - [x] Guild creation