From 34ac8f54ef9b62aa121c661d80358a5b57af5fe1 Mon Sep 17 00:00:00 2001 From: Paul Makles <paulmakles@gmail.com> Date: Mon, 18 Jan 2021 22:02:32 +0000 Subject: [PATCH] Add group creation. Sync channels to clients on creation. --- src/database/entities/channel.rs | 29 +++++++++-- src/database/entities/message.rs | 4 +- src/database/entities/user.rs | 2 +- src/notifications/events.rs | 2 + src/routes/channels/group_create.rs | 74 +++++++++++++++++++++++++++++ src/routes/channels/message_send.rs | 19 ++++---- src/routes/channels/mod.rs | 4 +- src/routes/users/open_dm.rs | 2 +- src/util/result.rs | 18 +++++-- src/util/variables.rs | 10 ++-- 10 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 src/routes/channels/group_create.rs diff --git a/src/database/entities/channel.rs b/src/database/entities/channel.rs index edf5cbf..797514b 100644 --- a/src/database/entities/channel.rs +++ b/src/database/entities/channel.rs @@ -1,9 +1,9 @@ -use crate::database::*; +use crate::{database::*, notifications::{events::ClientboundNotification, hive}}; use crate::util::result::{Error, Result}; -use mongodb::bson::to_document; use serde::{Deserialize, Serialize}; +use mongodb::bson::to_document; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type")] pub enum Channel { SavedMessages { @@ -20,6 +20,8 @@ pub enum Channel { Group { #[serde(rename = "_id")] id: String, + #[serde(skip_serializing_if = "Option::is_none")] + nonce: Option<String>, name: String, owner: String, description: String, @@ -36,7 +38,7 @@ impl Channel { } } - pub async fn save(&self) -> Result<()> { + pub async fn publish(self) -> Result<()> { get_collection("channels") .insert_one( to_document(&self).map_err(|_| Error::DatabaseError { @@ -50,6 +52,25 @@ impl Channel { operation: "insert_one", with: "channel", })?; + + // ! IMPORTANT FIXME: THESE SUBSCRIPTIONS SHOULD BE DONE FROM HIVE NOT HERE!!! + let channel_id = self.id().to_string(); + match &self { + Channel::SavedMessages { user, .. } => { + hive::subscribe_if_exists(user.clone(), channel_id.clone()).ok(); + } + Channel::DirectMessage { recipients, .. } | + Channel::Group { recipients, .. } => { + for recipient in recipients { + hive::subscribe_if_exists(recipient.clone(), channel_id.clone()).ok(); + } + } + } + + ClientboundNotification::ChannelCreate(self) + .publish(channel_id) + .await + .ok(); Ok(()) } diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs index 8009934..6aa692a 100644 --- a/src/database/entities/message.rs +++ b/src/database/entities/message.rs @@ -26,7 +26,7 @@ pub struct Message { pub previous_content: Vec<PreviousEntry>, }*/ -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Message { #[serde(rename = "_id")] pub id: String, @@ -41,7 +41,7 @@ pub struct Message { } impl Message { - pub async fn send(self) -> Result<()> { + pub async fn publish(self) -> Result<()> { get_collection("messages") .insert_one(to_bson(&self).unwrap().as_document().unwrap().clone(), None) .await diff --git a/src/database/entities/user.rs b/src/database/entities/user.rs index ca4c706..ba5af0f 100644 --- a/src/database/entities/user.rs +++ b/src/database/entities/user.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum RelationshipStatus { None, User, diff --git a/src/notifications/events.rs b/src/notifications/events.rs index 15beec5..2d22d85 100644 --- a/src/notifications/events.rs +++ b/src/notifications/events.rs @@ -42,6 +42,8 @@ pub enum ClientboundNotification { id: String, }, + ChannelCreate(Channel), + /*MessageCreate { id: String, nonce: Option<String>, diff --git a/src/routes/channels/group_create.rs b/src/routes/channels/group_create.rs new file mode 100644 index 0000000..6b2e517 --- /dev/null +++ b/src/routes/channels/group_create.rs @@ -0,0 +1,74 @@ +use crate::database::*; +use crate::util::result::{Error, Result}; +use crate::util::variables::MAX_GROUP_SIZE; + +use mongodb::bson::doc; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::iter::FromIterator; +use ulid::Ulid; +use validator::Validate; + +#[derive(Validate, Serialize, Deserialize)] +pub struct Data { + #[validate(length(min = 1, max = 32))] + name: String, + #[validate(length(min = 0, max = 1024))] + description: Option<String>, + // Maximum length of 36 allows both ULIDs and UUIDs. + #[validate(length(min = 1, max = 36))] + nonce: String, + users: Vec<String> +} + +#[post("/create", data = "<info>")] +pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> { + info + .validate() + .map_err(|error| Error::FailedValidation { error })?; + + let mut set: HashSet<String> = HashSet::from_iter(info.users.iter().cloned()); + set.insert(user.id.clone()); + + if set.len() > *MAX_GROUP_SIZE { + Err(Error::GroupTooLarge { max: *MAX_GROUP_SIZE })? + } + + if get_collection("channels") + .find_one( + doc! { + "nonce": &info.nonce + }, + None, + ) + .await + .map_err(|_| Error::DatabaseError { + operation: "find_one", + with: "channel", + })? + .is_some() + { + Err(Error::DuplicateNonce)? + } + + for target in &set { + if get_relationship(&user, target) != RelationshipStatus::Friend { + Err(Error::NotFriends)? + } + } + + let id = Ulid::new().to_string(); + let channel = Channel::Group { + id, + nonce: Some(info.nonce.clone()), + name: info.name.clone(), + description: info.description.clone().unwrap_or_else(|| "A group.".to_string()), + owner: user.id, + recipients: set.into_iter().collect::<Vec<String>>() + }; + + channel.clone().publish().await?; + + Ok(json!(channel)) +} diff --git a/src/routes/channels/message_send.rs b/src/routes/channels/message_send.rs index 6fbb340..3220d99 100644 --- a/src/routes/channels/message_send.rs +++ b/src/routes/channels/message_send.rs @@ -2,7 +2,7 @@ use crate::database::*; use crate::util::result::{Error, Result}; use mongodb::bson::doc; -use rocket_contrib::json::Json; +use rocket_contrib::json::{Json, JsonValue}; use serde::{Deserialize, Serialize}; use ulid::Ulid; use validator::Validate; @@ -17,7 +17,7 @@ pub struct Data { } #[post("/<target>/messages", data = "<message>")] -pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<()> { +pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<JsonValue> { message .validate() .map_err(|error| Error::FailedValidation { error })?; @@ -43,10 +43,10 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<()> { })? .is_some() { - Err(Error::AlreadySentMessage)? + Err(Error::DuplicateNonce)? } - Message { + let msg = Message { id: Ulid::new().to_string(), channel: target.id().to_string(), author: user.id, @@ -54,9 +54,12 @@ pub async fn req(user: User, target: Ref, message: Json<Data>) -> Result<()> { content: message.content.clone(), nonce: Some(message.nonce.clone()), edited: None, - } - .send() - .await?; + }; + + msg + .clone() + .publish() + .await?; - Ok(()) + Ok(json!(msg)) } diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs index dd20b77..f2a2f7e 100644 --- a/src/routes/channels/mod.rs +++ b/src/routes/channels/mod.rs @@ -7,6 +7,7 @@ mod message_edit; mod message_fetch; mod message_query; mod message_send; +mod group_create; pub fn routes() -> Vec<Route> { routes![ @@ -16,6 +17,7 @@ pub fn routes() -> Vec<Route> { message_query::req, message_fetch::req, message_edit::req, - message_delete::req + message_delete::req, + group_create::req ] } diff --git a/src/routes/users/open_dm.rs b/src/routes/users/open_dm.rs index 5a4c441..00e65c9 100644 --- a/src/routes/users/open_dm.rs +++ b/src/routes/users/open_dm.rs @@ -43,7 +43,7 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> { } }; - channel.save().await?; + channel.clone().publish().await?; Ok(json!(channel)) } } diff --git a/src/util/result.rs b/src/util/result.rs index 0f2dec3..5bf66c7 100644 --- a/src/util/result.rs +++ b/src/util/result.rs @@ -30,13 +30,17 @@ pub enum Error { Blocked, #[snafu(display("You have been blocked by this user."))] BlockedByOther, + #[snafu(display("Not friends with target user."))] + NotFriends, // ? Channel related errors. - #[snafu(display("Already sent a message with this nonce."))] - AlreadySentMessage, #[snafu(display("Cannot edit someone else's message."))] CannotEditMessage, - + #[snafu(display("Group size is too large."))] + GroupTooLarge { + max: usize + }, + // ? General errors. #[snafu(display("Failed to validate fields."))] FailedValidation { error: ValidationErrors }, @@ -47,6 +51,8 @@ pub enum Error { }, #[snafu(display("Internal server error."))] InternalError, + #[snafu(display("Already created an object with this nonce."))] + DuplicateNonce, #[snafu(display("This request had no effect."))] NoEffect, } @@ -67,13 +73,15 @@ impl<'r> Responder<'r, 'static> for Error { Error::AlreadySentRequest => Status::Conflict, Error::Blocked => Status::Conflict, Error::BlockedByOther => Status::Forbidden, + Error::NotFriends => Status::Forbidden, - Error::AlreadySentMessage => Status::Conflict, Error::CannotEditMessage => Status::Forbidden, - + Error::GroupTooLarge { .. } => Status::Forbidden, + Error::FailedValidation { .. } => Status::UnprocessableEntity, Error::DatabaseError { .. } => Status::InternalServerError, Error::InternalError => Status::InternalServerError, + Error::DuplicateNonce => Status::Conflict, Error::NoEffect => Status::Ok, }; diff --git a/src/util/variables.rs b/src/util/variables.rs index 9e630dd..7630e33 100644 --- a/src/util/variables.rs +++ b/src/util/variables.rs @@ -33,10 +33,14 @@ lazy_static! { pub static ref SMTP_HOST: String = env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string()); pub static ref SMTP_USERNAME: String = - env::var("SMTP_USERNAME").unwrap_or_else(|_| "".to_string()); + env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string()); pub static ref SMTP_PASSWORD: String = - env::var("SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()); - pub static ref SMTP_FROM: String = env::var("SMTP_FROM").unwrap_or_else(|_| "".to_string()); + env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string()); + pub static ref SMTP_FROM: String = env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string()); + + // Application Logic Settings + pub static ref MAX_GROUP_SIZE: usize = + env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap(); } pub fn preflight_checks() { -- GitLab