diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index e9cbda64bb3956152b2f4f962c3aafe5a745cf3f..dc2869a608d2158b52fa8d0751a997c5ba6a1318 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -186,6 +186,27 @@ impl Channel { with: "channel", })?; + // Remove from server object. + if let Channel::TextChannel { server, .. } = &self { + get_collection("servers") + .update_one( + doc !{ + "_id": server + }, + doc! { + "$pull": { + "channels": id + } + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_one", + with: "servers", + })?; + } + ClientboundNotification::ChannelDelete { id: id.to_string() }.publish(id.to_string()); if let Channel::Group { icon, .. } = self { diff --git a/src/database/entities/invites.rs b/src/database/entities/invites.rs index 1371bfbd2217b6854066c1579974093d9ef5997f..7574a178bd3e3f6382d500f031fc1ce432be2197 100644 --- a/src/database/entities/invites.rs +++ b/src/database/entities/invites.rs @@ -13,6 +13,7 @@ pub enum Invite { Server { #[serde(rename = "_id")] code: String, + server: String, creator: String, channel: String, }, @@ -35,6 +36,13 @@ impl Invite { } } + pub fn creator(&self) -> &String { + match &self { + Invite::Server { creator, .. } => creator, + Invite::Group { creator, .. } => creator, + } + } + pub async fn get(code: &str) -> Result<Invite> { let doc = get_collection("invites") .find_one(doc! { "_id": code }, None) diff --git a/src/database/entities/server.rs b/src/database/entities/server.rs index a8023d607df39de5255eda3c8a5856910b2b3fcc..fefdc0e9ddf53f82e2d0fcd84c89d1c64c932263 100644 --- a/src/database/entities/server.rs +++ b/src/database/entities/server.rs @@ -1,9 +1,11 @@ use crate::database::*; use crate::notifications::events::ClientboundNotification; use crate::util::result::{Error, Result}; +use mongodb::bson::Document; use mongodb::bson::doc; -use mongodb::bson::from_document; +use futures::StreamExt; use mongodb::bson::to_document; +use mongodb::options::FindOptions; use rocket_contrib::json::JsonValue; use serde::{Deserialize, Serialize}; @@ -26,7 +28,8 @@ pub struct Member { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Ban { - pub id: String, + #[serde(rename = "_id")] + pub id: MemberCompositeKey, pub reason: String, } @@ -84,7 +87,123 @@ impl Server { } pub async fn delete(&self) -> Result<()> { - unimplemented!() + let messages = get_collection("messages"); + + // Check if there are any attachments we need to delete. + let message_ids = messages + .find( + doc! { + "server": &self.id, + "attachment": { + "$exists": 1 + } + }, + FindOptions::builder().projection(doc! { "_id": 1 }).build(), + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "fetch_many", + with: "messages", + })? + .filter_map(async move |s| s.ok()) + .collect::<Vec<Document>>() + .await + .into_iter() + .filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string())) + .collect::<Vec<String>>(); + + // If we found any, mark them as deleted. + if message_ids.len() > 0 { + get_collection("attachments") + .update_many( + doc! { + "message_id": { + "$in": message_ids + } + }, + doc! { + "$set": { + "deleted": true + } + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "update_many", + with: "attachments", + })?; + } + + // And then delete said messages. + messages + .delete_many( + doc! { + "server": &self.id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_many", + with: "messages", + })?; + + // Delete all channels, members, bans and invites. + for with in [ "channels", "invites" ] { + get_collection(with) + .delete_many( + doc! { + "server": &self.id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_many", + with, + })?; + } + + for with in [ "server_members", "server_bans" ] { + get_collection(with) + .delete_many( + doc! { + "_id.server": &self.id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_many", + with, + })?; + } + + get_collection("servers") + .delete_one( + doc! { + "_id": &self.id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_one", + with: "server", + })?; + + ClientboundNotification::ServerDelete { id: self.id.clone() }.publish(self.id.clone()); + + if let Some(attachment) = &self.icon { + attachment.delete().await?; + } + + if let Some(attachment) = &self.banner { + attachment.delete().await?; + } + + Ok(()) } pub async fn join_member(&self, id: &str) -> Result<()> { diff --git a/src/database/guards/reference.rs b/src/database/guards/reference.rs index dda305413f022a080e09d8a9ef0698349457b60d..513042b0202f441cb0ee2272736867dc8017cb54 100644 --- a/src/database/guards/reference.rs +++ b/src/database/guards/reference.rs @@ -38,7 +38,7 @@ impl Ref { operation: "find_one", with: &collection, })? - .ok_or_else(|| Error::UnknownUser)?; + .ok_or_else(|| Error::NotFound)?; Ok(from_document::<T>(doc).map_err(|_| Error::DatabaseError { operation: "from_document", diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index 0fc7f7b7372bb0333ef57aba087a7cacb14d1f61..faf877e65b5b5cd27fbb4cb3d062c9ef3475c435 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -11,6 +11,7 @@ pub async fn req(user: User, target: Ref) -> Result<()> { .with_channel(&target) .for_channel() .await?; + if !perm.get_view() { Err(Error::MissingPermission)? } diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs index 5acf4b0c0c9f4097383029defd0651efd8ead66b..ef428182ac94cd289e9ed95ef16440189f8bac0c 100644 --- a/src/routes/channels/edit_channel.rs +++ b/src/routes/channels/edit_channel.rs @@ -45,7 +45,8 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> { } match &target { - Channel::Group { id, icon, .. } => { + Channel::Group { id, icon, .. } | + Channel::TextChannel { id, icon, .. } => { let mut set = doc! {}; let mut unset = doc! {}; @@ -107,42 +108,44 @@ pub async fn req(user: User, target: Ref, data: Json<Data>) -> Result<()> { } .publish(id.clone()); - if let Some(name) = data.name { - Message::create( - "00000000000000000000000000".to_string(), - id.clone(), - Content::SystemMessage(SystemMessage::ChannelRenamed { - name, - by: user.id.clone(), - }), - ) - .publish(&target) - .await - .ok(); - } + if let Channel::Group { .. } = &target { + if let Some(name) = data.name { + Message::create( + "00000000000000000000000000".to_string(), + id.clone(), + Content::SystemMessage(SystemMessage::ChannelRenamed { + name, + by: user.id.clone(), + }), + ) + .publish(&target) + .await + .ok(); + } - if let Some(_) = data.description { - Message::create( - "00000000000000000000000000".to_string(), - id.clone(), - Content::SystemMessage(SystemMessage::ChannelDescriptionChanged { - by: user.id.clone(), - }), - ) - .publish(&target) - .await - .ok(); - } + if let Some(_) = data.description { + Message::create( + "00000000000000000000000000".to_string(), + id.clone(), + Content::SystemMessage(SystemMessage::ChannelDescriptionChanged { + by: user.id.clone(), + }), + ) + .publish(&target) + .await + .ok(); + } - if let Some(_) = data.icon { - Message::create( - "00000000000000000000000000".to_string(), - id.clone(), - Content::SystemMessage(SystemMessage::ChannelIconChanged { by: user.id }), - ) - .publish(&target) - .await - .ok(); + if let Some(_) = data.icon { + Message::create( + "00000000000000000000000000".to_string(), + id.clone(), + Content::SystemMessage(SystemMessage::ChannelIconChanged { by: user.id }), + ) + .publish(&target) + .await + .ok(); + } } if remove_icon { diff --git a/src/routes/channels/fetch_channel.rs b/src/routes/channels/fetch_channel.rs index 75a7d5b0229dbb23f4bc8157102c81137e60e04e..cacb983c5dbb91f89c11bf630b7e783a7220e18f 100644 --- a/src/routes/channels/fetch_channel.rs +++ b/src/routes/channels/fetch_channel.rs @@ -11,6 +11,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { .with_channel(&target) .for_channel() .await?; + if !perm.get_view() { Err(Error::MissingPermission)? } diff --git a/src/routes/channels/invite_channel.rs b/src/routes/channels/invite_create.rs similarity index 91% rename from src/routes/channels/invite_channel.rs rename to src/routes/channels/invite_create.rs index 72001947a8daa7d832c9c341e19a0ce2e2124117..eaef59512610a9e5c8b4340ca72ade0c2b6e3021 100644 --- a/src/routes/channels/invite_channel.rs +++ b/src/routes/channels/invite_create.rs @@ -13,7 +13,7 @@ lazy_static! { ]; } -#[post("/<target>/invite")] +#[post("/<target>/invites")] pub async fn req(user: User, target: Ref) -> Result<JsonValue> { let target = target.fetch_channel().await?; let perm = permissions::PermissionCalculator::new(&user) @@ -30,10 +30,11 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { Channel::Group { .. } => { unimplemented!() } - Channel::TextChannel { id, .. } => { + Channel::TextChannel { id, server, .. } => { Invite::Server { code: code.clone(), creator: user.id, + server: server.clone(), channel: id.clone(), } .save() diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index f8b9be60582e4964926ae9c60671a2de809b98f8..cb7d0b4eea31e29bff3b67e403589808d3b27a44 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -7,7 +7,7 @@ mod fetch_members; mod group_add_member; mod group_create; mod group_remove_member; -mod invite_channel; +mod invite_create; mod join_call; mod message_delete; mod message_edit; @@ -22,7 +22,7 @@ pub fn routes() -> Vec<Route> { fetch_members::req, delete_channel::req, edit_channel::req, - invite_channel::req, + invite_create::req, message_send::req, message_query::req, message_query_stale::req, diff --git a/src/routes/invites/invite_delete.rs b/src/routes/invites/invite_delete.rs new file mode 100644 index 0000000000000000000000000000000000000000..ac3ea1227d7b3038e969465d44496840840cd8b5 --- /dev/null +++ b/src/routes/invites/invite_delete.rs @@ -0,0 +1,28 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +#[delete("/<target>")] +pub async fn req(user: User, target: Ref) -> Result<()> { + let target = target.fetch_invite().await?; + + if target.creator() == &user.id { + target.delete().await + } else { + match &target { + Invite::Server { server, .. } => { + let server = Ref::from_unchecked(server.clone()).fetch_server().await?; + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&server) + .for_server() + .await?; + + if !perm.get_manage_server() { + return Err(Error::MissingPermission); + } + + target.delete().await + }, + _ => unreachable!() + } + } +} diff --git a/src/routes/invites/invite_join.rs b/src/routes/invites/invite_join.rs index ab1b9141fc8e3dbeebcd95fbd364b8ea2376d552..cf7a601c6455bac5a95305138df63917a660caf4 100644 --- a/src/routes/invites/invite_join.rs +++ b/src/routes/invites/invite_join.rs @@ -22,6 +22,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { server.join_member(&user.id).await?; Ok(json!({ + "type": "Server", "channel": channel, "server": server })) diff --git a/src/routes/invites/mod.rs b/src/routes/invites/mod.rs index 426a0f27b468837cef0d1bdb7c345611c5616002..69843215b2699cd52b32dfc325a9f35ee840b1a1 100644 --- a/src/routes/invites/mod.rs +++ b/src/routes/invites/mod.rs @@ -1,8 +1,9 @@ use rocket::Route; +mod invite_delete; mod invite_fetch; mod invite_join; pub fn routes() -> Vec<Route> { - routes![invite_fetch::req, invite_join::req] + routes![invite_fetch::req, invite_join::req, invite_delete::req] } diff --git a/src/routes/servers/mod.rs b/src/routes/servers/mod.rs index 2b8d5c8bcde48265604db69bee8721ba5c9836bf..20ef08b0aa43dd86a99ef77d2dd1fd55139dae0e 100644 --- a/src/routes/servers/mod.rs +++ b/src/routes/servers/mod.rs @@ -2,6 +2,7 @@ use rocket::Route; mod server_create; mod server_delete; +mod server_fetch; mod server_edit; mod channel_create; @@ -10,6 +11,7 @@ pub fn routes() -> Vec<Route> { routes![ server_create::req, server_delete::req, + server_fetch::req, server_edit::req, channel_create::req ] diff --git a/src/routes/servers/server_delete.rs b/src/routes/servers/server_delete.rs index 970dc58e7d0dd5e047d6c62145019121150e62e5..29f13afcc7ccfbae130fd6945e7903971c6d7ef6 100644 --- a/src/routes/servers/server_delete.rs +++ b/src/routes/servers/server_delete.rs @@ -1,26 +1,42 @@ use crate::database::*; use crate::util::result::{Error, Result}; +use crate::notifications::events::ClientboundNotification; use mongodb::bson::doc; #[delete("/<target>")] pub async fn req(user: User, target: Ref) -> Result<()> { let target = target.fetch_server().await?; - - /*let perm = permissions::PermissionCalculator::new(&user) - .with_channel(&target) - .for_channel() + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() .await?; + if !perm.get_view() { - Err(Error::MissingPermission)? - }*/ + return Err(Error::MissingPermission); + } + + if user.id == target.owner { + target.delete().await + } else { + get_collection("server_members") + .delete_one( + doc! { - // ! FIXME: either delete server if owner - // ! OR leave server if member + }, + None + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_one", + with: "server_member" + })?; - // also need to delete server invites - // and members - // and bans + ClientboundNotification::ServerMemberLeave { + id: target.id.clone(), + user: user.id + }.publish(target.id); - target.delete().await + Ok(()) + } } diff --git a/src/routes/servers/server_fetch.rs b/src/routes/servers/server_fetch.rs new file mode 100644 index 0000000000000000000000000000000000000000..465b2ef0c6d9a9e61838459242cf4503bf39f34a --- /dev/null +++ b/src/routes/servers/server_fetch.rs @@ -0,0 +1,20 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; + +use rocket_contrib::json::JsonValue; + +#[get("/<target>")] +pub async fn req(user: User, target: Ref) -> Result<JsonValue> { + let target = target.fetch_server().await?; + + let perm = permissions::PermissionCalculator::new(&user) + .with_server(&target) + .for_server() + .await?; + + if !perm.get_view() { + Err(Error::MissingPermission)? + } + + Ok(json!(target)) +} diff --git a/src/util/result.rs b/src/util/result.rs index 922245b8c94f806d8f235963657fe4a0c6229f10..44d4bad66ec8ceebdb7cf61aa011c1e0c92d147d 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -38,7 +38,7 @@ pub enum Error { // ? Server related errors. UnknownServer, - + // ? General errors. TooManyIds, FailedValidation { @@ -54,6 +54,7 @@ pub enum Error { InvalidCredentials, DuplicateNonce, VosoUnavailable, + NotFound, NoEffect, } @@ -96,6 +97,7 @@ impl<'r> Responder<'r, 'static> for Error { Error::InvalidCredentials => Status::Forbidden, Error::DuplicateNonce => Status::Conflict, Error::VosoUnavailable => Status::BadRequest, + Error::NotFound => Status::NotFound, Error::NoEffect => Status::Ok, };