diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index 2481cd6..55d7799 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -63,7 +63,7 @@ pub mod login { login_result.token, cloned_limits, login_result.settings, - Some(object), + object, ); Ok(user) diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 8ca4735..d961fbf 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -71,7 +71,7 @@ pub mod register { token.clone(), cloned_limits, settings, - Some(user_object), + user_object, ); Ok(user) } diff --git a/src/api/guilds/member.rs b/src/api/guilds/member.rs new file mode 100644 index 0000000..b301b99 --- /dev/null +++ b/src/api/guilds/member.rs @@ -0,0 +1,91 @@ +use reqwest::Client; + +use crate::{instance::UserMeta, limit::LimitedRequester, types}; + +impl types::GuildMember { + /// Adds a role to a guild member. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a `UserMeta` instance. + /// * `guild_id` - The ID of the guild. + /// * `member_id` - The ID of the member. + /// * `role_id` - The ID of the role to add. + /// + /// # Returns + /// + /// An `Option` containing a `ChorusLibError` if the request fails, or `None` if the request succeeds. + pub async fn add_role( + user: &mut UserMeta, + guild_id: &str, + member_id: &str, + role_id: &str, + ) -> Option { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/guilds/{}/members/{}/roles/{}/", + belongs_to.urls.get_api(), + guild_id, + member_id, + role_id + ); + let request = Client::new().put(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; + if response.is_err() { + return Some(response.err().unwrap()); + } else { + return None; + } + } + + /// Removes a role from a guild member. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a `UserMeta` instance. + /// * `guild_id` - The ID of the guild. + /// * `member_id` - The ID of the member. + /// * `role_id` - The ID of the role to remove. + /// + /// # Returns + /// + /// An `Option` containing a `ChorusLibError` if the request fails, or `None` if the request succeeds. + pub async fn remove_role( + user: &mut UserMeta, + guild_id: &str, + member_id: &str, + role_id: &str, + ) -> Option { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!( + "{}/guilds/{}/members/{}/roles/{}/", + belongs_to.urls.get_api(), + guild_id, + member_id, + role_id + ); + let request = Client::new().delete(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; + if response.is_err() { + return Some(response.err().unwrap()); + } else { + return None; + } + } +} diff --git a/src/api/guilds/mod.rs b/src/api/guilds/mod.rs index b06487d..1138a50 100644 --- a/src/api/guilds/mod.rs +++ b/src/api/guilds/mod.rs @@ -1,5 +1,7 @@ pub mod guilds; +pub mod member; pub mod roles; pub use guilds::*; pub use roles::*; +pub use roles::*; diff --git a/src/api/guilds/roles.rs b/src/api/guilds/roles.rs index 482a859..a65b36d 100644 --- a/src/api/guilds/roles.rs +++ b/src/api/guilds/roles.rs @@ -106,4 +106,59 @@ impl types::RoleObject { }; Ok(role) } + + /// Updates the position of a role in the guild's hierarchy. + /// + /// # Arguments + /// + /// * `user` - A mutable reference to a [`UserMeta`] instance. + /// * `guild_id` - The ID of the guild to update the role position in. + /// * `role_position_update_schema` - A [`RolePositionUpdateSchema`] instance containing the new position 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 position_update( + user: &mut UserMeta, + guild_id: &str, + role_position_update_schema: types::RolePositionUpdateSchema, + ) -> Result { + let mut belongs_to = user.belongs_to.borrow_mut(); + let url = format!("{}/guilds/{}/roles/", belongs_to.urls.get_api(), guild_id); + let body = match to_string(&role_position_update_schema) { + Ok(body) => body, + Err(e) => { + return Err(ChorusLibError::FormCreationError { + error: e.to_string(), + }) + } + }; + let request = Client::new() + .patch(url) + .bearer_auth(user.token()) + .body(body); + let response = LimitedRequester::new() + .await + .send_request( + request, + crate::api::limits::LimitType::Guild, + &mut belongs_to.limits, + &mut user.limits, + ) + .await + .unwrap(); + let role: RoleObject = match from_str(&response.text().await.unwrap()) { + Ok(role) => role, + Err(e) => { + return Err(ChorusLibError::InvalidResponseError { + error: e.to_string(), + }) + } + }; + Ok(role) + } } diff --git a/src/api/users/users.rs b/src/api/users/users.rs index ffabd3f..1d35f32 100644 --- a/src/api/users/users.rs +++ b/src/api/users/users.rs @@ -72,10 +72,7 @@ impl UserMeta { Err(e) => return Err(e), }; let user_updated: User = from_str(&result.text().await.unwrap()).unwrap(); - let _ = std::mem::replace( - &mut self.object.as_mut().unwrap(), - &mut user_updated.clone(), - ); + let _ = std::mem::replace(&mut self.object, user_updated.clone()); Ok(user_updated) } diff --git a/src/instance.rs b/src/instance.rs index 6121c65..524735b 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -90,7 +90,7 @@ pub struct UserMeta { pub token: String, pub limits: Limits, pub settings: UserSettings, - pub object: Option, + pub object: User, } impl UserMeta { @@ -107,7 +107,7 @@ impl UserMeta { token: String, limits: Limits, settings: UserSettings, - object: Option, + object: User, ) -> UserMeta { UserMeta { belongs_to, diff --git a/src/types/entities/role.rs b/src/types/entities/role.rs index 63e99e3..7f527be 100644 --- a/src/types/entities/role.rs +++ b/src/types/entities/role.rs @@ -107,4 +107,30 @@ impl PermissionFlags { pub fn has_permission(&self, permission: PermissionFlags) -> bool { self.contains(permission) || self.contains(PermissionFlags::ADMINISTRATOR) } + + pub fn to_string(&self) -> String { + self.bits().to_string() + } + + /// Creates a String of Permissions from a given [`Vec`] of [`PermissionFlags`]. + /// # Example: + /// ``` + /// use chorus::types::{PermissionFlags}; + /// + /// let mut vector: Vec = Vec::new(); + /// vector.push(PermissionFlags::MUTE_MEMBERS); + /// vector.push(PermissionFlags::DEAFEN_MEMBERS); + /// + /// let permissions: String = PermissionFlags::from_vec(vector); + /// + /// println!("The permissions string is {}.", permissions); + /// assert_eq!(permissions, "12582912".to_string()); + /// ``` + pub fn from_vec(flags: Vec) -> String { + let mut permissions: PermissionFlags = Default::default(); + for flag in flags.iter() { + permissions = permissions | flag.clone(); + } + permissions.to_string() + } } diff --git a/src/types/schema/guild.rs b/src/types/schema/guild.rs index a607123..835f6ad 100644 --- a/src/types/schema/guild.rs +++ b/src/types/schema/guild.rs @@ -1,4 +1,4 @@ -use crate::types::{entities::Channel, Snowflake}; +use crate::types::entities::Channel; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] @@ -14,27 +14,3 @@ pub struct GuildCreateSchema { pub system_channel_id: Option, pub rules_channel_id: Option, } - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -/// Represents the schema which needs to be sent to create or modify a Role. -/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema) -pub struct RoleCreateModifySchema { - pub name: Option, - pub permissions: Option, - pub color: Option, - pub hoist: Option, - pub icon: Option>, - pub unicode_emoji: Option, - pub mentionable: Option, - pub position: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -/// 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, -} diff --git a/src/types/schema/mod.rs b/src/types/schema/mod.rs index 6fe3e37..2e5c4f0 100644 --- a/src/types/schema/mod.rs +++ b/src/types/schema/mod.rs @@ -3,6 +3,7 @@ mod auth; mod channel; mod guild; mod message; +mod role; mod user; pub use apierror::*; @@ -10,6 +11,7 @@ pub use auth::*; pub use channel::*; pub use guild::*; pub use message::*; +pub use role::*; pub use user::*; #[cfg(test)] diff --git a/src/types/schema/role.rs b/src/types/schema/role.rs new file mode 100644 index 0000000..97ab248 --- /dev/null +++ b/src/types/schema/role.rs @@ -0,0 +1,26 @@ +use crate::types::Snowflake; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +/// Represents the schema which needs to be sent to create or modify a Role. +/// See: [https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema](https://docs.spacebar.chat/routes/#cmp--schemas-rolemodifyschema) +pub struct RoleCreateModifySchema { + pub name: Option, + pub permissions: Option, + pub color: Option, + pub hoist: Option, + pub icon: Option>, + pub unicode_emoji: Option, + pub mentionable: Option, + pub position: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +/// 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, +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4b20399..c58c79a 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,9 @@ use chorus::{ instance::{Instance, UserMeta}, - types::{Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema}, + types::{ + Channel, ChannelCreateSchema, Guild, GuildCreateSchema, RegisterSchema, + RoleCreateModifySchema, RoleObject, + }, URLBundle, }; @@ -10,6 +13,7 @@ pub struct TestBundle { pub user: UserMeta, pub instance: Instance, pub guild: Guild, + pub role: RoleObject, pub channel: Channel, } @@ -70,11 +74,27 @@ pub async fn setup() -> TestBundle { .await .unwrap(); + let role_create_schema: chorus::types::RoleCreateModifySchema = RoleCreateModifySchema { + name: Some("Bundle role".to_string()), + permissions: Some("8".to_string()), // Administrator permissions + hoist: Some(true), + icon: None, + unicode_emoji: Some("".to_string()), + mentionable: Some(true), + position: None, + color: None, + }; + let guild_id = guild.id.clone().to_string(); + let role = chorus::types::RoleObject::create(&mut user, &guild_id, role_create_schema) + .await + .unwrap(); + TestBundle { urls, user, instance, guild, + role, channel, } } diff --git a/tests/member.rs b/tests/member.rs new file mode 100644 index 0000000..f4b22fd --- /dev/null +++ b/tests/member.rs @@ -0,0 +1,13 @@ +mod common; + +#[tokio::test] +async fn add_remove_role() { + let mut bundle = common::setup().await; + let guild_id = &bundle.guild.id.to_string(); + 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; + 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. + common::teardown(bundle).await +} diff --git a/tests/roles.rs b/tests/roles.rs index b34bdd7..3eee2b8 100644 --- a/tests/roles.rs +++ b/tests/roles.rs @@ -5,9 +5,11 @@ use chorus::types::{self, RoleCreateModifySchema}; #[tokio::test] async fn create_and_get_roles() { let mut bundle = common::setup().await; + let permissions = types::PermissionFlags::CONNECT | types::PermissionFlags::MANAGE_EVENTS; + let permissions = Some(permissions.to_string()); let role_create_schema: types::RoleCreateModifySchema = RoleCreateModifySchema { name: Some("cool person".to_string()), - permissions: Some("2251804225".to_string()), + permissions, hoist: Some(true), icon: None, unicode_emoji: Some("".to_string()), @@ -25,7 +27,7 @@ async fn create_and_get_roles() { .unwrap() .unwrap() .iter() - .nth(1) + .nth(2) .unwrap() .clone();