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