diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs index cdea97461d4c0e707bf515a91c8c05a216ae05cb..4fad9551f0437ea6a4eac3951b630929500bc1e3 100644 --- a/src/database/entities/server.rs +++ b/src/database/entities/server.rs @@ -258,4 +258,30 @@ impl Server { Ok(()) } + + pub async fn remove_member(&self, id: &str) -> Result<()> { + get_collection("server_members") + .delete_one( + doc! { + "_id": { + "server": &self.id, + "user": &id + } + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_one", + with: "server_members", + })?; + + ClientboundNotification::ServerMemberLeave { + id: self.id.clone(), + user: id.to_string() + } + .publish(self.id.clone()); + + Ok(()) + } } diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs index 21c427dc2d212a09954728343cdf765727ea3d1b..6ffd106a3b13e00dabefd9b039e5e8a23398977f 100644 --- a/src/database/entities/user.rs +++ b/src/database/entities/user.rs @@ -28,14 +28,14 @@ pub enum RelationshipStatus { BlockedOther, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Relationship { #[serde(rename = "_id")] pub id: String, pub status: RelationshipStatus, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum Presence { Online, Idle, @@ -43,7 +43,7 @@ pub enum Presence { Invisible, } -#[derive(Validate, Serialize, Deserialize, Debug)] +#[derive(Validate, Serialize, Deserialize, Debug, Clone)] pub struct UserStatus { #[validate(length(min = 1, max = 128))] #[serde(skip_serializing_if = "Option::is_none")] @@ -52,7 +52,7 @@ pub struct UserStatus { pub presence: Option<Presence>, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct UserProfile { #[serde(skip_serializing_if = "Option::is_none")] pub content: Option<String>, @@ -72,7 +72,7 @@ pub enum Badges { impl_op_ex_commutative!(+ |a: &i32, b: &Badges| -> i32 { *a | *b as i32 }); // When changing this struct, update notifications/payload.rs#80 -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct User { #[serde(rename = "_id")] pub id: String, diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs index 2e73aee82e00b90c624a1af8e2c43ae776fb10ad..276e2483a3c3c0df09d0f1ad3345cbd3b2429464 100644 --- a/src/database/permissions/server.rs +++ b/src/database/permissions/server.rs @@ -9,7 +9,8 @@ use std::ops; #[repr(u32)] pub enum ServerPermission { View = 0b00000000000000000000000000000001, // 1 - // 2 bits of space + ManageMembers = 0b00000000000000000000000000000010, // 2 + ManageChannels = 0b00000000000000000000000000000100, // 4 ManageServer = 0b00000000000000000000000000001000, // 8 // 8 bits of space ChangeNickname = 0b00000000000000000001000000000000, // 4096 @@ -26,6 +27,8 @@ bitfield! { pub struct ServerPermissions(MSB0 [u32]); u32; pub get_view, _: 31; + pub get_manage_members, _: 30; + pub get_manage_channels, _: 29; pub get_manage_server, _: 28; pub get_change_nickname, _: 19; diff --git a/src/notifications/events.rs b/src/notifications/events.rs index 4d7ec2551073dc34b238d2ecd3ef92056db54d4f..52fee1317c095714f1569a368db97ad2b1828475 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -11,7 +11,7 @@ use crate::{ util::result::{Error, Result}, }; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "error")] pub enum WebSocketError { LabelMe, @@ -29,7 +29,7 @@ pub enum ServerboundNotification { EndTyping { channel: String }, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum RemoveUserField { ProfileContent, ProfileBackground, @@ -37,26 +37,26 @@ pub enum RemoveUserField { Avatar, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum RemoveChannelField { Icon, Description, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum RemoveServerField { Icon, Banner, Description, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum RemoveMemberField { Nickname, Avatar } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type")] pub enum ClientboundNotification { Error(WebSocketError), @@ -210,7 +210,7 @@ pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<() Ok(()) } -pub fn posthandle_hook(notification: &ClientboundNotification) { +pub async fn posthandle_hook(notification: &ClientboundNotification) { match ¬ification { ClientboundNotification::ChannelDelete { id } => { get_hive().hive.drop_topic(&id).ok(); @@ -218,7 +218,7 @@ pub fn posthandle_hook(notification: &ClientboundNotification) { ClientboundNotification::ChannelGroupLeave { id, user } => { get_hive() .hive - .unsubscribe(&user.to_string(), &id.to_string()) + .unsubscribe(user, id) .ok(); } ClientboundNotification::ServerDelete { id } => { @@ -227,14 +227,23 @@ pub fn posthandle_hook(notification: &ClientboundNotification) { ClientboundNotification::ServerMemberLeave { id, user } => { get_hive() .hive - .unsubscribe(&user.to_string(), &id.to_string()) + .unsubscribe(user, id) .ok(); + + if let Ok(server) = Ref::from_unchecked(id.clone()).fetch_server().await { + for channel in server.channels { + get_hive() + .hive + .unsubscribe(user, &channel) + .ok(); + } + } } ClientboundNotification::UserRelationship { id, user, status } => { if status == &RelationshipStatus::None { get_hive() .hive - .unsubscribe(&id.to_string(), &user.id.to_string()) + .unsubscribe(id, &user.id) .ok(); } } diff --git a/src/notifications/hive.rs b/src/notifications/hive.rs index 3541e5308f7bab1344195280d20bf193a73eaf0b..591c6d852037834454cfc049e7a70d34626b134a 100644 --- a/src/notifications/hive.rs +++ b/src/notifications/hive.rs @@ -13,8 +13,11 @@ static HIVE: OnceCell<Hive> = OnceCell::new(); pub async fn init_hive() { let hive = MongodbPubSub::new( - |ids, notification| { - super::events::posthandle_hook(¬ification); + |ids, notification: ClientboundNotification| { + let notif = notification.clone(); + async_std::task::spawn(async move { + super::events::posthandle_hook(¬if).await; + }); if let Ok(data) = to_string(¬ification) { debug!("Pushing out notification. {}", data); diff --git a/src/routes/servers/member_remove.rs b/src/routes/servers/member_remove.rs new file mode 100644 index 0000000000000000000000000000000000000000..645dccff65ef83e2e1a2a56da7eb1a1d2f90b044 --- /dev/null +++ b/src/routes/servers/member_remove.rs @@ -0,0 +1,25 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use mongodb::bson::doc; + +#[delete("/<target>/members/<member>")] +pub async fn req(user: User, target: Ref, member: String) -> Result<()> { + let target = target.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_members() { + return Err(Error::MissingPermission) + } + + let member = Ref::from(member)?.fetch_member(&target.id).await?; + if member.id.user == user.id { + return Err(Error::InvalidOperation) + } + + target.remove_member(&member.id.user).await +} diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs index 195081e77b580043b2870207a80c772080f987b6..cf867c56fa86709347da5946fcf3f3c630a39e92 100644 --- a/src/routes/servers/mod.rs +++ b/src/routes/servers/mod.rs @@ -8,6 +8,7 @@ mod server_edit; mod channel_create; mod member_fetch_all; +mod member_remove; mod member_fetch; mod member_edit; @@ -21,6 +22,7 @@ pub fn routes() -> Vec<Route> { server_edit::req, channel_create::req, member_fetch_all::req, + member_remove::req, member_fetch::req, member_edit::req, invites_fetch::req