From 32cd9d8a13180f15dece91087e850ec633ca3d2c Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Wed, 31 Mar 2021 20:54:47 +0100 Subject: [PATCH] Strict typing for system messages; add a way to rename group. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/database/entities/message.rs | 78 +++++++++++++++++----- src/database/permissions/channel.rs | 8 ++- src/routes/channels/delete_channel.rs | 3 +- src/routes/channels/edit_channel.rs | 71 ++++++++++++++++++++ src/routes/channels/group_add_member.rs | 3 +- src/routes/channels/group_remove_member.rs | 3 +- src/routes/channels/message_send.rs | 3 +- src/routes/channels/mod.rs | 2 + src/routes/root.rs | 2 +- 11 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 src/routes/channels/edit_channel.rs diff --git a/Cargo.lock b/Cargo.lock index 5ecd6de..5d88e98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2475,7 +2475,7 @@ dependencies = [ [[package]] name = "revolt" -version = "0.4.0-alpha.0" +version = "0.4.0-alpha.1" dependencies = [ "async-std", "async-tungstenite", diff --git a/Cargo.toml b/Cargo.toml index 3a8eb6f..6cbdf5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt" -version = "0.4.0-alpha.0" +version = "0.4.0-alpha.1" authors = ["Paul Makles <paulmakles@gmail.com>"] edition = "2018" diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 06913b2..7191f16 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -17,6 +17,47 @@ use web_push::{ ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder, }; +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "type")] +pub enum MessageEmbed { + Dummy +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(tag = "type")] +pub enum SystemMessage { + #[serde(rename = "text")] + Text { + content: String + }, + #[serde(rename = "user_added")] + UserAdded { + id: String, + by: String + }, + #[serde(rename = "user_remove")] + UserRemove { + id: String, + by: String + }, + #[serde(rename = "user_left")] + UserLeft { + id: String + }, + #[serde(rename = "channel_renamed")] + ChannelRenamed { + name: String, + by: String + }, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum Content { + Text(String), + SystemMessage(SystemMessage) +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Message { #[serde(rename = "_id")] @@ -26,15 +67,17 @@ pub struct Message { pub channel: String, pub author: String, - pub content: String, + pub content: Content, #[serde(skip_serializing_if = "Option::is_none")] pub attachment: Option<File>, #[serde(skip_serializing_if = "Option::is_none")] pub edited: Option<DateTime>, + #[serde(skip_serializing_if = "Option::is_none")] + pub embeds: Option<MessageEmbed>, } impl Message { - pub fn create(author: String, channel: String, content: String) -> Message { + pub fn create(author: String, channel: String, content: Content) -> Message { Message { id: Ulid::new().to_string(), nonce: None, @@ -44,6 +87,7 @@ impl Message { content, attachment: None, edited: None, + embeds: None } } @@ -56,22 +100,28 @@ impl Message { with: "message", })?; + let mut set = if let Content::Text(text) = &self.content { + doc! { + "last_message": { + "_id": self.id.clone(), + "author": self.author.clone(), + "short": text.chars().take(24).collect::<String>() + } + } + } else { + doc! {} + }; + // ! FIXME: temp code let channels = get_collection("channels"); match &channel { Channel::DirectMessage { id, .. } => { + set.insert("active", true); channels .update_one( doc! { "_id": id }, doc! { - "$set": { - "active": true, - "last_message": { - "_id": self.id.clone(), - "author": self.author.clone(), - "short": self.content.chars().take(24).collect::<String>() - } - } + "$set": set }, None, ) @@ -86,13 +136,7 @@ impl Message { .update_one( doc! { "_id": id }, doc! { - "$set": { - "last_message": { - "_id": self.id.clone(), - "author": self.author.clone(), - "short": self.content.chars().take(24).collect::<String>() - } - } + "$set": set }, None, ) diff --git a/src/database/permissions/channel.rs b/src/database/permissions/channel.rs index e0c9c46..a163247 100644 --- a/src/database/permissions/channel.rs +++ b/src/database/permissions/channel.rs @@ -12,7 +12,8 @@ pub enum ChannelPermission { View = 1, SendMessage = 2, ManageMessages = 4, - VoiceCall = 8, + ManageChannel = 8, + VoiceCall = 16, } bitfield! { @@ -21,7 +22,8 @@ bitfield! { pub get_view, _: 31; pub get_send_message, _: 30; pub get_manage_messages, _: 29; - pub get_voice_call, _: 28; + pub get_manage_channel, _: 28; + pub get_voice_call, _: 27; } impl_op_ex!(+ |a: &ChannelPermission, b: &ChannelPermission| -> u32 { *a as u32 | *b as u32 }); @@ -69,7 +71,7 @@ impl<'a> PermissionCalculator<'a> { .find(|x| *x == &self.perspective.id) .is_some() { - Ok(ChannelPermission::View + ChannelPermission::SendMessage + ChannelPermission::VoiceCall) + Ok(ChannelPermission::View + ChannelPermission::SendMessage + ChannelPermission::ManageChannel + ChannelPermission::VoiceCall) } else { Ok(0) } diff --git a/src/routes/channels/delete_channel.rs b/src/routes/channels/delete_channel.rs index 5c41580..f7fa08b 100644 --- a/src/routes/channels/delete_channel.rs +++ b/src/routes/channels/delete_channel.rs @@ -102,8 +102,7 @@ pub async fn req(user: User, target: Ref) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - // ! FIXME: make a schema for this - format!("{{\"type\":\"user_left\",\"id\":\"{}\"}}", user.id), + Content::SystemMessage(SystemMessage::UserLeft { id: user.id }) ) .publish(&target) .await diff --git a/src/routes/channels/edit_channel.rs b/src/routes/channels/edit_channel.rs new file mode 100644 index 0000000..d51cbcd --- /dev/null +++ b/src/routes/channels/edit_channel.rs @@ -0,0 +1,71 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; +use crate::notifications::events::ClientboundNotification; + +use validator::Validate; +use rocket_contrib::json::Json; +use serde::{Serialize, Deserialize}; +use mongodb::bson::{doc, to_document}; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 32))] + name: Option<String>, + #[validate(length(min = 0, max = 1024))] + description: Option<String>, +} + +#[patch("/<target>", data = "<info>")] +pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<()> { + info.validate() + .map_err(|error| Error::FailedValidation { error })?; + + if info.name.is_none() && info.description.is_none() { + return Ok(()) + } + + let target = target.fetch_channel().await?; + let perm = permissions::PermissionCalculator::new(&user) + .with_channel(&target) + .for_channel() + .await?; + + if !perm.get_manage_channel() { + Err(Error::MissingPermission)? + } + + match &target { + Channel::Group { id, .. } => { + let col = get_collection("channels"); + col.update_one( + doc! { "_id": &id }, + doc! { "$set": to_document(&info.0).map_err(|_| Error::DatabaseError { operation: "to_document", with: "info" })? }, + None + ) + .await + .map_err(|_| Error::DatabaseError { operation: "update_one", with: "channel" })?; + + ClientboundNotification::ChannelUpdate { + id: id.clone(), + data: json!(info.0) + } + .publish(id.clone()) + .await + .ok(); + + if let Some(name) = &info.name { + Message::create( + "00000000000000000000000000".to_string(), + id.clone(), + Content::SystemMessage(SystemMessage::ChannelRenamed { name: name.clone(), by: user.id }) + ) + .publish(&target) + .await + .ok(); + } + + Ok(()) + } + _ => Err(Error::InvalidOperation) + } +} diff --git a/src/routes/channels/group_add_member.rs b/src/routes/channels/group_add_member.rs index 1d5c9cd..8fc2d8b 100644 --- a/src/routes/channels/group_add_member.rs +++ b/src/routes/channels/group_add_member.rs @@ -59,8 +59,7 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - // ! FIXME: make a schema for this - format!("{{\"type\":\"user_added\",\"id\":\"{}\",\"by\":\"{}\"}}", member.id, user.id), + Content::SystemMessage(SystemMessage::UserAdded { id: member.id, by: user.id }) ) .publish(&channel) .await diff --git a/src/routes/channels/group_remove_member.rs b/src/routes/channels/group_remove_member.rs index 491a19a..9cc2541 100644 --- a/src/routes/channels/group_remove_member.rs +++ b/src/routes/channels/group_remove_member.rs @@ -56,8 +56,7 @@ pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> { Message::create( "00000000000000000000000000".to_string(), id.clone(), - // ! FIXME: make a schema for this - format!("{{\"type\":\"user_remove\",\"id\":\"{}\",\"by\":\"{}\"}}", member.id, user.id), + Content::SystemMessage(SystemMessage::UserRemove { id: member.id, by: user.id }) ) .publish(&channel) .await diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs index 452770a..6803ca5 100644 --- a/src/routes/channels/message_send.rs +++ b/src/routes/channels/message_send.rs @@ -115,10 +115,11 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonVal channel: target.id().to_string(), author: user.id, - content: message.content.clone(), + content: Content::Text(message.content.clone()), attachment, nonce: Some(message.nonce.clone()), edited: None, + embeds: None }; msg.clone().publish(&target).await?; diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index 180ceef..365474c 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -1,6 +1,7 @@ use rocket::Route; mod delete_channel; +mod edit_channel; mod fetch_channel; mod group_add_member; mod group_create; @@ -17,6 +18,7 @@ pub fn routes() -> Vec<Route> { routes![ fetch_channel::req, delete_channel::req, + edit_channel::req, message_send::req, message_query::req, message_query_stale::req, diff --git a/src/routes/root.rs b/src/routes/root.rs index 4dbee03..7f8940f 100644 --- a/src/routes/root.rs +++ b/src/routes/root.rs @@ -9,7 +9,7 @@ use rocket_contrib::json::JsonValue; #[get("/")] pub async fn root() -> JsonValue { json!({ - "revolt": "0.4.0-alpha.0", + "revolt": "0.4.0-alpha.1", "features": { "registration": !*DISABLE_REGISTRATION, "captcha": { -- GitLab