diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs index f9c1605512cf71526fcd97d88c134a972ae65ea1..7833ccdd6c8ae708c53a8aeb1cb84567870bf5b7 100644 --- a/src/database/entities/user.rs +++ b/src/database/entities/user.rs @@ -1,4 +1,5 @@ use futures::StreamExt; +use mongodb::bson::Document; use mongodb::options::{Collation, FindOneOptions}; use mongodb::{ bson::{doc, from_document}, @@ -223,4 +224,31 @@ impl User { Ok(users) } + + /// Utility function to get all the server IDs the user is in. + pub async fn fetch_server_ids(&self) -> Result<Vec<String>> { + Ok(get_collection("server_members") + .find( + doc! { + "_id.user": &self.id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find", + with: "server_members", + })? + .filter_map(async move |s| s.ok()) + .collect::<Vec<Document>>() + .await + .into_iter() + .filter_map(|x| { + x.get_document("_id") + .ok() + .map(|i| i.get_str("server").ok().map(|x| x.to_string())) + }) + .flatten() + .collect::<Vec<String>>()) + } } diff --git a/src/database/permissions/mod.rs b/src/database/permissions/mod.rs index 376a440896e1dc5e257c3b0f64669bb490a52112..9219906ab710b75fd516dfa1034ccd6438edcceb 100644 --- a/src/database/permissions/mod.rs +++ b/src/database/permissions/mod.rs @@ -1,6 +1,7 @@ pub use crate::database::*; pub mod channel; +pub mod server; pub mod user; pub use user::get_relationship; diff --git a/src/database/permissions/server.rs b/src/database/permissions/server.rs index 94166d70b0758ed2a76085981c169a515d6e2b74..b2321006395770c1579a5c8a226a16c97319ef86 100644 --- a/src/database/permissions/server.rs +++ b/src/database/permissions/server.rs @@ -1,4 +1,3 @@ -use crate::database::*; use crate::util::result::Result; use super::PermissionCalculator; @@ -10,6 +9,7 @@ use std::ops; #[repr(u32)] pub enum ServerPermission { View = 1, + ManageServer = 8, } impl_op_ex!(+ |a: &ServerPermission, b: &ServerPermission| -> u32 { *a as u32 | *b as u32 }); @@ -19,6 +19,7 @@ bitfield! { pub struct ServerPermissions(MSB0 [u32]); u32; pub get_view, _: 31; + pub get_manage_server, _: 28; } impl<'a> PermissionCalculator<'a> { @@ -29,14 +30,14 @@ impl<'a> PermissionCalculator<'a> { unreachable!() }; - if &self.perspective.id == server.owner { + if self.perspective.id == server.owner { Ok(u32::MAX) } else { Ok(ServerPermission::View as u32) } } - pub async fn for_server(self) -> Result<ChannelPermissions<[u32; 1]>> { + pub async fn for_server(self) -> Result<ServerPermissions<[u32; 1]>> { Ok(ServerPermissions([self.calculate_server().await?])) } } diff --git a/src/notifications/payload.rs b/src/notifications/payload.rs index 84daf3ee2547788101b7c3af99265cf39fcf7e3b..ac5c45395b27e9ddeafd2a1be9aa892eb5985a44 100644 --- a/src/notifications/payload.rs +++ b/src/notifications/payload.rs @@ -6,7 +6,7 @@ use crate::{ util::result::{Error, Result}, }; use futures::StreamExt; -use mongodb::bson::{doc, from_document, Document}; +use mongodb::bson::{doc, from_document}; pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> { let mut user_ids: HashSet<String> = HashSet::new(); @@ -19,30 +19,7 @@ pub async fn generate_ready(mut user: User) -> Result<ClientboundNotification> { ); } - let server_ids = get_collection("server_members") - .find( - doc! { - "_id.user": &user.id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "find", - with: "server_members", - })? - .filter_map(async move |s| s.ok()) - .collect::<Vec<Document>>() - .await - .into_iter() - .filter_map(|x| { - x.get_document("_id") - .ok() - .map(|i| i.get_str("server").ok().map(|x| x.to_string())) - }) - .flatten() - .collect::<Vec<String>>(); - + let server_ids = user.fetch_server_ids().await?; let mut cursor = get_collection("servers") .find( doc! { diff --git a/src/notifications/subscriptions.rs b/src/notifications/subscriptions.rs index df07afe35abef8f72454cf8f5dbf4510e3a99f40..fc770f727ef8bc84dafbb293c18e4029a36fb170 100644 --- a/src/notifications/subscriptions.rs +++ b/src/notifications/subscriptions.rs @@ -45,7 +45,14 @@ pub async fn generate_subscriptions(user: &User) -> Result<(), String> { } } - // ! FIXME: fetch memberships for servers + let server_ids = user + .fetch_server_ids() + .await + .map_err(|_| "Failed to fetch memberships.".to_string())?; + + for id in server_ids { + hive.subscribe(user.id.clone(), id)?; + } Ok(()) } diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs index 5b2fbd25c1beef8c79b9423439cfeba15b73a78d..5acf4b0c0c9f4097383029defd0651efd8ead66b 100644 --- a/src/routes/channels/edit_channel.rs +++ b/src/routes/channels/edit_channel.rs @@ -69,7 +69,7 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> { if let Some(attachment_id) = &data.icon { let attachment = - File::find_and_use(&attachment_id, "icons", "object", &user.id).await?; + File::find_and_use(&attachment_id, "icons", "object", target.id()).await?; set.insert( "icon", to_document(&attachment).map_err(|_| Error::DatabaseError { diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs index 74d3b848cd7c3dfd4e09cda0ebd628dbddc4eed7..a6d64cd770dc078b526996187c44da1e80b91049 100644 --- a/src/routes/servers/mod.rs +++ b/src/routes/servers/mod.rs @@ -2,7 +2,8 @@ use rocket::Route; mod server_create; mod server_delete; +mod server_edit; pub fn routes() -> Vec<Route> { - routes![server_create::req, server_delete::req] + routes![server_create::req, server_delete::req, server_edit::req] } diff --git a/src/routes/servers/server_delete.rs b/src/routes/servers/server_delete.rs index 91d8ec7f467932dbb2c3352bf4657463391a7a48..983fcaa9b4c0ed0591e18a486204f934535e564c 100644 --- a/src/routes/servers/server_delete.rs +++ b/src/routes/servers/server_delete.rs @@ -1,5 +1,5 @@ -use crate::util::result::{Error, Result}; use crate::database::*; +use crate::util::result::{Error, Result}; use mongodb::bson::doc; @@ -15,5 +15,8 @@ pub async fn req(user: User, target: Ref) -> Result<()> { Err(Error::MissingPermission)? }*/ + // ! FIXME: either delete server if owner + // ! OR leave server if member + target.delete().await } diff --git a/src/routes/servers/server_edit.rs b/src/routes/servers/server_edit.rs new file mode 100644 index 0000000000000000000000000000000000000000..4fe9e9db4128b106556cbeb4f780636ff2d8a08b --- /dev/null +++ b/src/routes/servers/server_edit.rs @@ -0,0 +1,131 @@ +use crate::notifications::events::ClientboundNotification; +use crate::util::result::{Error, Result}; +use crate::{database::*, notifications::events::RemoveServerField}; + +use mongodb::bson::{doc, to_document}; +use rocket_contrib::json::Json; +use serde::{Deserialize, Serialize}; +use validator::Validate; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 32))] + #[serde(skip_serializing_if = "Option::is_none")] + name: Option<String>, + icon: Option<String>, + banner: Option<String>, + remove: Option<RemoveServerField>, +} + +#[patch("/<target>", data = "<data>")] +pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> { + let data = data.into_inner(); + data.validate() + .map_err(|error| Error::FailedValidation { error })?; + + if data.name.is_none() && data.icon.is_none() && data.banner.is_none() && data.remove.is_none() + { + return Ok(()); + } + + let target = target.fetch_server().await?; + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_manage_server() { + Err(Error::MissingPermission)? + } + + let mut set = doc! {}; + let mut unset = doc! {}; + + let mut remove_icon = false; + let mut remove_banner = false; + if let Some(remove) = &data.remove { + match remove { + RemoveServerField::Icon => { + unset.insert("icon", 1); + remove_icon = true; + } + RemoveServerField::Banner => { + unset.insert("banner", 1); + remove_banner = true; + } + } + } + + if let Some(name) = &data.name { + set.insert("name", name); + } + + if let Some(attachment_id) = &data.icon { + let attachment = File::find_and_use(&attachment_id, "icons", "object", &target.id).await?; + set.insert( + "icon", + to_document(&attachment).map_err(|_| Error::DatabaseError { + operation: "to_document", + with: "attachment", + })?, + ); + + remove_icon = true; + } + + if let Some(attachment_id) = &data.banner { + let attachment = + File::find_and_use(&attachment_id, "banners", "server", &target.id).await?; + set.insert( + "banner", + to_document(&attachment).map_err(|_| Error::DatabaseError { + operation: "to_document", + with: "attachment", + })?, + ); + + remove_banner = true; + } + + let mut operations = doc! {}; + if set.len() > 0 { + operations.insert("$set", &set); + } + + if unset.len() > 0 { + operations.insert("$unset", unset); + } + + if operations.len() > 0 { + get_collection("servers") + .update_one(doc! { "_id": &target.id }, operations, None) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "server", + })?; + } + + ClientboundNotification::ServerUpdate { + id: target.id.clone(), + data: json!(set), + clear: data.remove, + } + .publish(target.id.clone()); + + let Server { icon, banner, .. } = target; + + if remove_icon { + if let Some(old_icon) = icon { + old_icon.delete().await?; + } + } + + if remove_banner { + if let Some(old_banner) = banner { + old_banner.delete().await?; + } + } + + Ok(()) +}