From f9fbe54b1720a80610cbf6589bcf90fa8747af50 Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Sat, 5 Jun 2021 22:41:47 +0100 Subject: [PATCH] Servers: Create and delete invites. Create and fetch servers. --- src/database/entities/channel.rs | 21 +++ src/database/entities/invites.rs | 8 ++ src/database/entities/server.rs | 125 +++++++++++++++++- src/database/guards/reference.rs | 2 +- src/routes/channels/delete_channel.rs | 1 + src/routes/channels/edit_channel.rs | 73 +++++----- src/routes/channels/fetch_channel.rs | 1 + .../{invite_channel.rs => invite_create.rs} | 5 +- src/routes/channels/mod.rs | 4 +- src/routes/invites/invite_delete.rs | 28 ++++ src/routes/invites/invite_join.rs | 1 + src/routes/invites/mod.rs | 3 +- src/routes/servers/mod.rs | 2 + src/routes/servers/server_delete.rs | 40 ++++-- src/routes/servers/server_fetch.rs | 20 +++ src/util/result.rs | 4 +- 16 files changed, 281 insertions(+), 57 deletions(-) rename src/routes/channels/{invite_channel.rs => invite_create.rs} (91%) create mode 100644 src/routes/invites/invite_delete.rs create mode 100644 src/routes/servers/server_fetch.rs diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index e9cbda6..dc2869a 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 1371bfb..7574a17 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 a8023d6..fefdc0e 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 dda3054..513042b 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 0fc7f7b..faf877e 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 5acf4b0..ef42818 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 75a7d5b..cacb983 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 7200194..eaef595 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 f8b9be6..cb7d0b4 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 0000000..ac3ea12 --- /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 ab1b914..cf7a601 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 426a0f2..6984321 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 2b8d5c8..20ef08b 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 970dc58..29f13af 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 0000000..465b2ef --- /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 922245b..44d4bad 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, }; -- GitLab