diff --git a/Cargo.lock b/Cargo.lock index 5ecd6de0c163279d647305bc71060450b2bb14e7..5d88e985f482995cbebd42d551f594de689f9d82 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 3a8eb6f0d73fcbeb4be33babf436d21d993be35f..6cbdf5b3c4f482b0f160ebb6c7f18e4e09e350d7 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 06913b28ff86da256a595b43b5c597519a1eb8fb..7191f1644813796a9991d9ddd9e75750338b3336 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 e0c9c460104e4884095aea9ec501288a28091f7a..a1632476d5189991a35f947fe692e23943af3e22 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 5c41580e3622eccd5333275f2c742e8e54bd19c4..f7fa08b84d3fbacba5dcf12f63f2ca50dab0f7c1 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 0000000000000000000000000000000000000000..d51cbcd18f953d3253c1d2e27bbc9d534f50a911 --- /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 1d5c9cd0ef672a9115a63d75dd7f5fae4605a9b7..8fc2d8bc7b0775705bcb4b06bba63e91bfc6443d 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 491a19a7df080b070ac2f5c32155932ac9faeaed..9cc25417258ea8263e2cbb83761dec60c2d92716 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 452770ac1c2042e710a2687fef2053440e230d3e..6803ca5f08bb8f0beaaddbc32f90df3a8709b1d1 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 180ceef0e7ecd10e6f0f69c1d052e6ae725360e4..365474c334b185ce499deb43fed23d8d2929c44d 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 4dbee031483e0829ee7a795e08d2e0bea1f1e57e..7f8940f31331a2f7ae0bec49dfac7f05b7bee308 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": {