From 9a417aa6ad42370c889eaab49a4da2e358a41891 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 23 Jun 2021 13:50:18 +0100 Subject: [PATCH] Messaging: Add message replies. Servers: Add VoiceChannel. --- set_version.sh | 2 +- src/database/entities/channel.rs | 170 ++++++++++++--------- src/database/entities/message.rs | 5 + src/database/permissions/channel.rs | 4 +- src/notifications/events.rs | 3 +- src/routes/channels/delete_channel.rs | 3 +- src/routes/channels/invite_create.rs | 3 +- src/routes/channels/message_delete.rs | 1 + src/routes/channels/message_edit.rs | 2 + src/routes/channels/message_fetch.rs | 1 + src/routes/channels/message_query.rs | 1 + src/routes/channels/message_query_stale.rs | 1 + src/routes/channels/message_send.rs | 64 ++++++-- src/routes/servers/channel_create.rs | 43 ++++-- src/util/result.rs | 4 + src/version.rs | 2 +- 16 files changed, 208 insertions(+), 101 deletions(-) diff --git a/set_version.sh b/set_version.sh index d003f77c..e84cc49d 100755 --- a/set_version.sh +++ b/set_version.sh @@ -1,3 +1,3 @@ #!/bin/bash -export version=0.5.0-alpha.5 +export version=0.5.1-alpha.0 echo "pub const VERSION: &str = \"${version}\";" > src/version.rs diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index 5f102b2b..97cee87c 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -66,6 +66,19 @@ pub enum Channel { #[serde(skip_serializing_if = "Option::is_none")] last_message: Option, }, + VoiceChannel { + #[serde(rename = "_id")] + id: String, + server: String, + #[serde(skip_serializing_if = "Option::is_none")] + nonce: Option, + + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + icon: Option, + }, } impl Channel { @@ -74,7 +87,17 @@ impl Channel { Channel::SavedMessages { id, .. } | Channel::DirectMessage { id, .. } | Channel::Group { id, .. } - | Channel::TextChannel { id, .. } => id, + | Channel::TextChannel { id, .. } + | Channel::VoiceChannel { id, .. } => id, + } + } + pub fn has_messaging(&self) -> Result<()> { + match self { + Channel::SavedMessages { .. } + | Channel::DirectMessage { .. } + | Channel::Group { .. } + | Channel::TextChannel { .. } => Ok(()), + Channel::VoiceChannel { .. } => Err(Error::InvalidOperation) } } @@ -129,80 +152,85 @@ impl Channel { with: "channel_invites", })?; - // Delete any unreads. - get_collection("channel_unreads") - .delete_many( - doc! { - "_id.channel": id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "channel_unreads", - })?; + match &self { + Channel::VoiceChannel { .. } => {}, + _ => { + // Delete any unreads. + get_collection("channel_unreads") + .delete_many( + doc! { + "_id.channel": id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_many", + with: "channel_unreads", + })?; - // Check if there are any attachments we need to delete. - let message_ids = messages - .find( - doc! { - "channel": 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::>() - .await - .into_iter() - .filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string())) - .collect::>(); + // Check if there are any attachments we need to delete. + let message_ids = messages + .find( + doc! { + "channel": 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::>() + .await + .into_iter() + .filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string())) + .collect::>(); - // 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", - })?; + // 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! { + "channel": id + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "delete_many", + with: "messages", + })?; + } } - // And then delete said messages. - messages - .delete_many( - doc! { - "channel": id - }, - None, - ) - .await - .map_err(|_| Error::DatabaseError { - operation: "delete_many", - with: "messages", - })?; - // Remove from server object. if let Channel::TextChannel { server, .. } = &self { let server = Ref::from_unchecked(server.clone()).fetch_server().await?; diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 675ad35e..b7335158 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -57,6 +57,7 @@ impl Content { target.id().to_string(), self, None, + None ) .publish(&target) .await @@ -81,6 +82,8 @@ pub struct Message { pub embeds: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub mentions: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub replies: Option> } impl Message { @@ -89,6 +92,7 @@ impl Message { channel: String, content: Content, mentions: Option>, + replies: Option>, ) -> Message { Message { id: Ulid::new().to_string(), @@ -101,6 +105,7 @@ impl Message { edited: None, embeds: None, mentions, + replies } } diff --git a/src/database/permissions/channel.rs b/src/database/permissions/channel.rs index c47a21b0..e05f4ce9 100644 --- a/src/database/permissions/channel.rs +++ b/src/database/permissions/channel.rs @@ -84,7 +84,8 @@ impl<'a> PermissionCalculator<'a> { Ok(0) } } - Channel::TextChannel { server, .. } => { + Channel::TextChannel { server, .. } + | Channel::VoiceChannel { server, .. } => { let server = Ref::from_unchecked(server.clone()).fetch_server().await?; if self.perspective.id == server.owner { @@ -92,6 +93,7 @@ impl<'a> PermissionCalculator<'a> { } else { Ok(ChannelPermission::View + ChannelPermission::SendMessage + + ChannelPermission::VoiceCall + ChannelPermission::InviteOthers) } } diff --git a/src/notifications/events.rs b/src/notifications/events.rs index b26b7c5b..3cc091cc 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -178,7 +178,8 @@ pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<() subscribe_if_exists(recipient.clone(), channel_id.to_string()).ok(); } } - Channel::TextChannel { server, .. } => { + Channel::TextChannel { server, .. } + | Channel::VoiceChannel { server, .. } => { // ! FIXME: write a better algorithm? let members = Server::fetch_member_ids(server).await?; for member in members { diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index d11d8fe7..6bff3df9 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -105,7 +105,8 @@ pub async fn req(user: User, target: Ref) -> Result<()> { Ok(()) } - Channel::TextChannel { .. } => { + Channel::TextChannel { .. } | + Channel::VoiceChannel { .. } => { if perm.get_manage_channel() { target.delete().await } else { diff --git a/src/routes/channels/invite_create.rs b/src/routes/channels/invite_create.rs index eaef5951..0018b3b4 100644 --- a/src/routes/channels/invite_create.rs +++ b/src/routes/channels/invite_create.rs @@ -30,7 +30,8 @@ pub async fn req(user: User, target: Ref) -> Result { Channel::Group { .. } => { unimplemented!() } - Channel::TextChannel { id, server, .. } => { + Channel::TextChannel { id, server, .. } + | Channel::VoiceChannel { id, server, .. } => { Invite::Server { code: code.clone(), creator: user.id, diff --git a/src/routes/channels/message_delete.rs b/src/routes/channels/message_delete.rs index fa794d77..c0f3ac57 100644 --- a/src/routes/channels/message_delete.rs +++ b/src/routes/channels/message_delete.rs @@ -6,6 +6,7 @@ use mongodb::bson::doc; #[delete("//messages/")] pub async fn req(user: User, target: Ref, msg: Ref) -> Result<()> { let channel = target.fetch_channel().await?; + channel.has_messaging()?; let perm = permissions::PermissionCalculator::new(&user) .with_channel(&channel) diff --git a/src/routes/channels/message_edit.rs b/src/routes/channels/message_edit.rs index 19d95be3..a356a682 100644 --- a/src/routes/channels/message_edit.rs +++ b/src/routes/channels/message_edit.rs @@ -19,6 +19,8 @@ pub async fn req(user: User, target: Ref, msg: Ref, edit: Json) -> Result< .map_err(|error| Error::FailedValidation { error })?; let channel = target.fetch_channel().await?; + channel.has_messaging()?; + let perm = permissions::PermissionCalculator::new(&user) .with_channel(&channel) .for_channel() diff --git a/src/routes/channels/message_fetch.rs b/src/routes/channels/message_fetch.rs index d308c84a..c87a3334 100644 --- a/src/routes/channels/message_fetch.rs +++ b/src/routes/channels/message_fetch.rs @@ -6,6 +6,7 @@ use rocket_contrib::json::JsonValue; #[get("//messages/")] pub async fn req(user: User, target: Ref, msg: Ref) -> Result { let channel = target.fetch_channel().await?; + channel.has_messaging()?; let perm = permissions::PermissionCalculator::new(&user) .with_channel(&channel) diff --git a/src/routes/channels/message_query.rs b/src/routes/channels/message_query.rs index e1d67b80..4492e839 100644 --- a/src/routes/channels/message_query.rs +++ b/src/routes/channels/message_query.rs @@ -38,6 +38,7 @@ pub async fn req(user: User, target: Ref, options: Form) -> Result) -> Result>, + replies: Option>, } lazy_static! { @@ -25,6 +34,7 @@ lazy_static! { #[post("//messages", data = "")] pub async fn req(user: User, target: Ref, message: Json) -> Result { + let message = message.into_inner(); message .validate() .map_err(|error| Error::FailedValidation { error })?; @@ -36,6 +46,8 @@ pub async fn req(user: User, target: Ref, message: Json) -> Result) -> Result= 5 { + return Err(Error::TooManyReplies) + } + + for Reply { id, mention } in entries { + let message = Ref::from_unchecked(id) + .fetch_message(&target) + .await?; + + replies.insert(message.id); + + if mention { + mentions.insert(message.author); + } + } + } + + let mut attachments = vec![]; + if let Some(ids) = &message.attachments { // ! FIXME: move this to app config if ids.len() >= 5 { return Err(Error::TooManyAttachments) } - let mut attachments = vec![]; for attachment_id in ids { attachments .push(File::find_and_use(attachment_id, "attachments", "message", &id).await?); } - - Some(attachments) - } else { - None - }; - - let mut mentions = vec![]; - if let Some(captures) = RE_ULID.captures_iter(&message.content).next() { - // ! FIXME: in the future, verify in group so we can send out push - mentions.push(captures[1].to_string()); } - let msg = Message { id, channel: target.id().to_string(), author: user.id, content: Content::Text(message.content.clone()), - attachments, nonce: Some(message.nonce.clone()), edited: None, embeds: None, + + attachments: if attachments.len() > 0 { Some(attachments) } else { None }, mentions: if mentions.len() > 0 { - Some(mentions) + Some(mentions.into_iter().collect::>()) + } else { + None + }, + replies: if replies.len() > 0 { + Some(replies.into_iter().collect::>()) } else { None }, diff --git a/src/routes/servers/channel_create.rs b/src/routes/servers/channel_create.rs index d98bec0a..c96d1cdc 100644 --- a/src/routes/servers/channel_create.rs +++ b/src/routes/servers/channel_create.rs @@ -7,8 +7,22 @@ use serde::{Deserialize, Serialize}; use ulid::Ulid; use validator::Validate; +#[derive(Serialize, Deserialize)] +enum ChannelType { + Text, + Voice +} + +impl Default for ChannelType { + fn default() -> Self { + ChannelType::Text + } +} + #[derive(Validate, Serialize, Deserialize)] pub struct Data { + #[serde(rename = "type", default = "ChannelType::default")] + channel_type: ChannelType, #[validate(length(min = 1, max = 32))] name: String, #[validate(length(min = 0, max = 1024))] @@ -30,7 +44,7 @@ pub async fn req(user: User, target: Ref, info: Json) -> Result .for_server() .await?; - if !perm.get_manage_server() { + if !perm.get_manage_channels() { Err(Error::MissingPermission)? } @@ -52,15 +66,26 @@ pub async fn req(user: User, target: Ref, info: Json) -> Result } let id = Ulid::new().to_string(); - let channel = Channel::TextChannel { - id: id.clone(), - server: target.id.clone(), - nonce: Some(info.nonce), + let channel = match info.channel_type { + ChannelType::Text => Channel::TextChannel { + id: id.clone(), + server: target.id.clone(), + nonce: Some(info.nonce), - name: info.name, - description: info.description, - icon: None, - last_message: None, + name: info.name, + description: info.description, + icon: None, + last_message: None, + }, + ChannelType::Voice => Channel::VoiceChannel { + id: id.clone(), + server: target.id.clone(), + nonce: Some(info.nonce), + + name: info.name, + description: info.description, + icon: None + } }; channel.clone().publish().await?; diff --git a/src/util/result.rs b/src/util/result.rs index e0df9750..218a5369 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -26,9 +26,11 @@ pub enum Error { // ? Channel related errors. UnknownChannel, UnknownAttachment, + UnknownMessage, CannotEditMessage, CannotJoinCall, TooManyAttachments, + TooManyReplies, EmptyMessage, CannotRemoveYourself, GroupTooLarge { @@ -79,10 +81,12 @@ impl<'r> Responder<'r, 'static> for Error { Error::NotFriends => Status::Forbidden, Error::UnknownChannel => Status::NotFound, + Error::UnknownMessage => Status::NotFound, Error::UnknownAttachment => Status::BadRequest, Error::CannotEditMessage => Status::Forbidden, Error::CannotJoinCall => Status::BadRequest, Error::TooManyAttachments => Status::BadRequest, + Error::TooManyReplies => Status::BadRequest, Error::EmptyMessage => Status::UnprocessableEntity, Error::CannotRemoveYourself => Status::BadRequest, Error::GroupTooLarge { .. } => Status::Forbidden, diff --git a/src/version.rs b/src/version.rs index 0c8c63df..0625890f 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "0.5.0-alpha.5"; +pub const VERSION: &str = "0.5.1-alpha.0";