From c21d7c46207787398a3939cacaf4fc5151164d3d Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Tue, 19 Jan 2021 09:02:18 +0000
Subject: [PATCH] Group member add and remove routes.

---
 src/database/entities/message.rs           | 13 ++++
 src/database/permissions/channel.rs        |  8 ++-
 src/notifications/events.rs                | 52 +---------------
 src/routes/channels/group_add_member.rs    | 70 ++++++++++++++++++++++
 src/routes/channels/group_create.rs        | 12 ++--
 src/routes/channels/group_remove_member.rs | 69 +++++++++++++++++++++
 src/routes/channels/mod.rs                 |  6 +-
 src/util/result.rs                         | 12 ++++
 8 files changed, 184 insertions(+), 58 deletions(-)
 create mode 100644 src/routes/channels/group_add_member.rs
 create mode 100644 src/routes/channels/group_remove_member.rs

diff --git a/src/database/entities/message.rs b/src/database/entities/message.rs
index 6aa692a..ed3bd0f 100644
--- a/src/database/entities/message.rs
+++ b/src/database/entities/message.rs
@@ -5,6 +5,7 @@ use crate::{
 };
 use mongodb::bson::{to_bson, DateTime};
 use serde::{Deserialize, Serialize};
+use ulid::Ulid;
 
 /*#[derive(Serialize, Deserialize, Debug)]
 pub struct PreviousEntry {
@@ -41,6 +42,18 @@ pub struct Message {
 }
 
 impl Message {
+    pub fn create(author: String, channel: String, content: String) -> Message {
+        Message {
+            id: Ulid::new().to_string(),
+            nonce: None,
+            channel,
+            author,
+
+            content,
+            edited: None,
+        }
+    }
+
     pub async fn publish(self) -> Result<()> {
         get_collection("messages")
             .insert_one(to_bson(&self).unwrap().as_document().unwrap().clone(), None)
diff --git a/src/database/permissions/channel.rs b/src/database/permissions/channel.rs
index 41656bd..7c216b0 100644
--- a/src/database/permissions/channel.rs
+++ b/src/database/permissions/channel.rs
@@ -49,6 +49,12 @@ pub async fn calculate(user: &User, target: &Channel) -> ChannelPermissions<[u32
 
             ChannelPermissions([0])
         }
-        _ => unreachable!(),
+        Channel::Group { recipients, .. } => {
+            if recipients.iter().find(|x| *x == &user.id).is_some() {
+                ChannelPermissions([ChannelPermission::View + ChannelPermission::SendMessage])
+            } else {
+                ChannelPermissions([0])
+            }
+        }
     }
 }
diff --git a/src/notifications/events.rs b/src/notifications/events.rs
index 2d22d85..b408d62 100644
--- a/src/notifications/events.rs
+++ b/src/notifications/events.rs
@@ -43,68 +43,20 @@ pub enum ClientboundNotification {
     },
 
     ChannelCreate(Channel),
-
-    /*MessageCreate {
-        id: String,
-        nonce: Option<String>,
-        channel: String,
-        author: String,
-        content: String,
-    },
-
-    MessageEdit {
-        id: String,
-        channel: String,
-        author: String,
-        content: String,
-    },
-
-    MessageDelete {
-        id: String,
-    },
-
-    GroupUserJoin {
-        id: String,
-        user: String,
-    },
-
-    GroupUserLeave {
+    ChannelGroupJoin {
         id: String,
         user: String,
     },
-
-    GuildUserJoin {
+    ChannelGroupLeave {
         id: String,
         user: String,
     },
 
-    GuildUserLeave {
-        id: String,
-        user: String,
-        banned: bool,
-    },
-
-    GuildChannelCreate {
-        id: String,
-        channel: String,
-        name: String,
-        description: String,
-    },
-
-    GuildChannelDelete {
-        id: String,
-        channel: String,
-    },
-
-    GuildDelete {
-        id: String,
-    },*/
     UserRelationship {
         id: String,
         user: String,
         status: RelationshipStatus,
     },
-
     UserPresence {
         id: String,
         online: bool,
diff --git a/src/routes/channels/group_add_member.rs b/src/routes/channels/group_add_member.rs
new file mode 100644
index 0000000..677fdd8
--- /dev/null
+++ b/src/routes/channels/group_add_member.rs
@@ -0,0 +1,70 @@
+use crate::util::result::{Error, Result};
+use crate::util::variables::MAX_GROUP_SIZE;
+use crate::{database::*, notifications::events::ClientboundNotification};
+
+use mongodb::bson::doc;
+
+#[put("/<target>/recipients/<member>")]
+pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
+    if get_relationship(&user, &member.id) != RelationshipStatus::Friend {
+        Err(Error::NotFriends)?
+    }
+
+    let channel = target.fetch_channel().await?;
+
+    let perm = permissions::channel::calculate(&user, &channel).await;
+    if !perm.get_view() {
+        Err(Error::LabelMe)?
+    }
+
+    if let Channel::Group { id, recipients, .. } = channel {
+        if recipients.len() >= *MAX_GROUP_SIZE {
+            Err(Error::GroupTooLarge {
+                max: *MAX_GROUP_SIZE,
+            })?
+        }
+
+        if recipients.iter().find(|x| *x == &member.id).is_some() {
+            Err(Error::AlreadyInGroup)?
+        }
+
+        get_collection("channels")
+            .update_one(
+                doc! {
+                    "_id": &id
+                },
+                doc! {
+                    "$push": {
+                        "recipients": &member.id
+                    }
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "update_one",
+                with: "channel",
+            })?;
+
+        ClientboundNotification::ChannelGroupJoin {
+            id: id.clone(),
+            user: member.id.clone(),
+        }
+        .publish(id.clone())
+        .await
+        .ok();
+
+        Message::create(
+            "00000000000000000000000000".to_string(),
+            id,
+            format!("<@{}> added <@{}> to the group.", user.id, member.id),
+        )
+        .publish()
+        .await
+        .ok();
+
+        Ok(())
+    } else {
+        Err(Error::InvalidOperation)
+    }
+}
diff --git a/src/routes/channels/group_create.rs b/src/routes/channels/group_create.rs
index 5dbe89d..54075a6 100644
--- a/src/routes/channels/group_create.rs
+++ b/src/routes/channels/group_create.rs
@@ -36,6 +36,12 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
         })?
     }
 
+    for target in &set {
+        if get_relationship(&user, target) != RelationshipStatus::Friend {
+            Err(Error::NotFriends)?
+        }
+    }
+
     if get_collection("channels")
         .find_one(
             doc! {
@@ -53,12 +59,6 @@ pub async fn req(user: User, info: Json<Data>) -> Result<JsonValue> {
         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,
diff --git a/src/routes/channels/group_remove_member.rs b/src/routes/channels/group_remove_member.rs
new file mode 100644
index 0000000..438756d
--- /dev/null
+++ b/src/routes/channels/group_remove_member.rs
@@ -0,0 +1,69 @@
+use crate::util::result::{Error, Result};
+use crate::{database::*, notifications::events::ClientboundNotification};
+
+use mongodb::bson::doc;
+
+#[delete("/<target>/recipients/<member>")]
+pub async fn req(user: User, target: Ref, member: Ref) -> Result<()> {
+    if &user.id == &member.id {
+        Err(Error::CannotRemoveYourself)?
+    }
+
+    let channel = target.fetch_channel().await?;
+
+    if let Channel::Group {
+        id,
+        owner,
+        recipients,
+        ..
+    } = channel
+    {
+        if &user.id != &owner {
+            // figure out if we want to use perm system here
+            Err(Error::LabelMe)?
+        }
+
+        if recipients.iter().find(|x| *x == &member.id).is_none() {
+            Err(Error::NotInGroup)?
+        }
+
+        get_collection("channels")
+            .update_one(
+                doc! {
+                    "_id": &id
+                },
+                doc! {
+                    "$pull": {
+                        "recipients": &member.id
+                    }
+                },
+                None,
+            )
+            .await
+            .map_err(|_| Error::DatabaseError {
+                operation: "update_one",
+                with: "channel",
+            })?;
+
+        ClientboundNotification::ChannelGroupLeave {
+            id: id.clone(),
+            user: member.id.clone(),
+        }
+        .publish(id.clone())
+        .await
+        .ok();
+
+        Message::create(
+            "00000000000000000000000000".to_string(),
+            id,
+            format!("<@{}> removed <@{}> from the group.", user.id, member.id),
+        )
+        .publish()
+        .await
+        .ok();
+
+        Ok(())
+    } else {
+        Err(Error::InvalidOperation)
+    }
+}
diff --git a/src/routes/channels/mod.rs b/src/routes/channels/mod.rs
index 4c9d5a8..ccc1389 100644
--- a/src/routes/channels/mod.rs
+++ b/src/routes/channels/mod.rs
@@ -2,7 +2,9 @@ use rocket::Route;
 
 mod delete_channel;
 mod fetch_channel;
+mod group_add_member;
 mod group_create;
+mod group_remove_member;
 mod message_delete;
 mod message_edit;
 mod message_fetch;
@@ -18,6 +20,8 @@ pub fn routes() -> Vec<Route> {
         message_fetch::req,
         message_edit::req,
         message_delete::req,
-        group_create::req
+        group_create::req,
+        group_add_member::req,
+        group_remove_member::req
     ]
 }
diff --git a/src/util/result.rs b/src/util/result.rs
index 0de30d8..7aa3e03 100644
--- a/src/util/result.rs
+++ b/src/util/result.rs
@@ -36,8 +36,14 @@ pub enum Error {
     // ? Channel related errors.
     #[snafu(display("Cannot edit someone else's message."))]
     CannotEditMessage,
+    #[snafu(display("Cannot remove yourself from a group, use delete channel instead."))]
+    CannotRemoveYourself,
     #[snafu(display("Group size is too large."))]
     GroupTooLarge { max: usize },
+    #[snafu(display("User already part of group."))]
+    AlreadyInGroup,
+    #[snafu(display("User is not part of the group."))]
+    NotInGroup,
 
     // ? General errors.
     #[snafu(display("Failed to validate fields."))]
@@ -49,6 +55,8 @@ pub enum Error {
     },
     #[snafu(display("Internal server error."))]
     InternalError,
+    #[snafu(display("Operation cannot be performed on this object."))]
+    InvalidOperation,
     #[snafu(display("Already created an object with this nonce."))]
     DuplicateNonce,
     #[snafu(display("This request had no effect."))]
@@ -74,11 +82,15 @@ impl<'r> Responder<'r, 'static> for Error {
             Error::NotFriends => Status::Forbidden,
 
             Error::CannotEditMessage => Status::Forbidden,
+            Error::CannotRemoveYourself => Status::BadRequest,
             Error::GroupTooLarge { .. } => Status::Forbidden,
+            Error::AlreadyInGroup => Status::Conflict,
+            Error::NotInGroup => Status::NotFound,
 
             Error::FailedValidation { .. } => Status::UnprocessableEntity,
             Error::DatabaseError { .. } => Status::InternalServerError,
             Error::InternalError => Status::InternalServerError,
+            Error::InvalidOperation => Status::BadRequest,
             Error::DuplicateNonce => Status::Conflict,
             Error::NoEffect => Status::Ok,
         };
-- 
GitLab