From a910bd657ae13886754d19eec014d76e2276a966 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Fri, 10 Apr 2020 13:09:04 +0100
Subject: [PATCH] Add / remove from group + clean up code.

---
 src/database/message.rs |   4 +
 src/guards/auth.rs      |  17 ----
 src/guards/channel.rs   |  18 ----
 src/guards/guild.rs     |  18 ----
 src/routes/channel.rs   | 195 ++++++++++++++++++++++++++++++++--------
 src/routes/guild.rs     |   6 +-
 src/routes/mod.rs       |   2 +
 src/util/mod.rs         |   2 +-
 8 files changed, 170 insertions(+), 92 deletions(-)

diff --git a/src/database/message.rs b/src/database/message.rs
index 0e18d23..dd273a7 100644
--- a/src/database/message.rs
+++ b/src/database/message.rs
@@ -20,3 +20,7 @@ pub struct Message {
 
     pub previous_content: Option<Vec<PreviousEntry>>,
 }
+
+// ? TODO: write global send message
+// ? pub fn send_message();
+// ? handle websockets?
diff --git a/src/guards/auth.rs b/src/guards/auth.rs
index c9b2974..3c503df 100644
--- a/src/guards/auth.rs
+++ b/src/guards/auth.rs
@@ -160,20 +160,3 @@ impl<'r> FromParam<'r> for UserRef {
         }
     }
 }
-
-impl<'r> FromParam<'r> for User {
-    type Error = &'r RawStr;
-
-    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
-        let col = database::get_collection("users");
-        let result = col
-            .find_one(doc! { "_id": param.to_string() }, None)
-            .unwrap();
-
-        if let Some(user) = result {
-            Ok(from_bson(bson::Bson::Document(user)).expect("Failed to unwrap user."))
-        } else {
-            Err(param)
-        }
-    }
-}
diff --git a/src/guards/channel.rs b/src/guards/channel.rs
index 10f4116..7b64e2b 100644
--- a/src/guards/channel.rs
+++ b/src/guards/channel.rs
@@ -6,26 +6,8 @@ use serde::{Deserialize, Serialize};
 
 use crate::database;
 
-use database::channel::Channel;
 use database::message::Message;
 
-impl<'r> FromParam<'r> for Channel {
-    type Error = &'r RawStr;
-
-    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
-        let col = database::get_collection("channels");
-        let result = col
-            .find_one(doc! { "_id": param.to_string() }, None)
-            .unwrap();
-
-        if let Some(channel) = result {
-            Ok(from_bson(bson::Bson::Document(channel)).expect("Failed to unwrap channel."))
-        } else {
-            Err(param)
-        }
-    }
-}
-
 #[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct ChannelRef {
     #[serde(rename = "_id")]
diff --git a/src/guards/guild.rs b/src/guards/guild.rs
index aa57c55..1ca1f09 100644
--- a/src/guards/guild.rs
+++ b/src/guards/guild.rs
@@ -5,7 +5,6 @@ use rocket::request::FromParam;
 use serde::{Deserialize, Serialize};
 
 use crate::database;
-use crate::database::guild::Guild;
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct GuildRef {
@@ -74,20 +73,3 @@ impl<'r> FromParam<'r> for GuildRef {
         }
     }
 }
-
-impl<'r> FromParam<'r> for Guild {
-    type Error = &'r RawStr;
-
-    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
-        let col = database::get_collection("guilds");
-        let result = col
-            .find_one(doc! { "_id": param.to_string() }, None)
-            .unwrap();
-
-        if let Some(guild) = result {
-            Ok(from_bson(bson::Bson::Document(guild)).expect("Failed to unwrap guild."))
-        } else {
-            Err(param)
-        }
-    }
-}
diff --git a/src/routes/channel.rs b/src/routes/channel.rs
index 0b6b3d1..e2a5e52 100644
--- a/src/routes/channel.rs
+++ b/src/routes/channel.rs
@@ -1,7 +1,7 @@
 use super::Response;
 use crate::database::{
-    self, get_relationship_internal, message::Message, Permission, PermissionCalculator,
-    Relationship,
+    self, get_relationship, get_relationship_internal, message::Message, Permission,
+    PermissionCalculator, Relationship,
 };
 use crate::guards::auth::UserRef;
 use crate::guards::channel::ChannelRef;
@@ -9,7 +9,7 @@ use crate::util::vec_to_set;
 
 use bson::{doc, from_bson, Bson, Bson::UtcDatetime};
 use chrono::prelude::*;
-use hashbrown::HashSet;
+use mongodb::options::FindOptions;
 use num_enum::TryFromPrimitive;
 use rocket_contrib::json::Json;
 use serde::{Deserialize, Serialize};
@@ -64,40 +64,61 @@ pub fn create_group(user: UserRef, info: Json<CreateGroup>) -> Response {
         return Response::BadRequest(json!({ "error": "Group already created!" }));
     }
 
-    let relationships = user.fetch_relationships();
+    let mut query = vec![];
+    for item in &set {
+        if item == &user.id {
+            continue;
+        }
 
-    let mut users = vec![];
-    for item in set {
-        if let Some(target) = UserRef::from(item) {
-            if get_relationship_internal(&user.id, &target.id, &relationships)
-                != Relationship::Friend
-            {
+        query.push(Bson::String(item.clone()));
+    }
+
+    if let Ok(result) = database::get_collection("users").find(
+        doc! {
+            "_id": {
+                "$in": &query
+            }
+        },
+        FindOptions::builder().limit(query.len() as i64).build(),
+    ) {
+        if result.count() != query.len() {
+            return Response::BadRequest(json!({ "error": "Specified non-existant user(s)." }));
+        }
+
+        let relationships = user.fetch_relationships();
+        for item in set {
+            if item == user.id {
+                continue;
+            }
+
+            if get_relationship_internal(&user.id, &item, &relationships) != Relationship::Friend {
                 return Response::BadRequest(json!({ "error": "Not friends with user(s)." }));
             }
+        }
+
+        query.push(Bson::String(user.id.clone()));
 
-            users.push(Bson::String(target.id));
+        let id = Ulid::new().to_string();
+        if col
+            .insert_one(
+                doc! {
+                    "_id": id.clone(),
+                    "nonce": nonce,
+                    "type": ChannelType::GROUPDM as u32,
+                    "recipients": &query,
+                    "name": name,
+                    "owner": &user.id,
+                },
+                None,
+            )
+            .is_ok()
+        {
+            Response::Success(json!({ "id": id }))
         } else {
-            return Response::BadRequest(json!({ "error": "Specified non-existant user(s)." }));
+            Response::InternalServerError(json!({ "error": "Failed to create guild channel." }))
         }
-    }
-
-    let id = Ulid::new().to_string();
-    if col
-        .insert_one(
-            doc! {
-                "_id": id.clone(),
-                "nonce": nonce,
-                "type": ChannelType::GROUPDM as u32,
-                "recipients": users,
-                "name": name,
-            },
-            None,
-        )
-        .is_ok()
-    {
-        Response::Success(json!({ "id": id }))
     } else {
-        Response::InternalServerError(json!({ "error": "Failed to create guild channel." }))
+        Response::InternalServerError(json!({ "error": "Failed to validate users." }))
     }
 }
 
@@ -150,6 +171,102 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
     }
 }
 
+/// [groups] add user to channel
+#[put("/<target>/recipients/<member>")]
+pub fn add_member(user: UserRef, target: ChannelRef, member: UserRef) -> Option<Response> {
+    if target.channel_type != 1 {
+        return Some(Response::BadRequest(json!({ "error": "Not a group DM." })));
+    }
+
+    with_permissions!(user, target);
+
+    let recp = target.recipients.unwrap();
+    if recp.len() == 50 {
+        return Some(Response::BadRequest(
+            json!({ "error": "Maximum group size is 50." }),
+        ));
+    }
+
+    let set = vec_to_set(&recp);
+    if set.get(&member.id).is_some() {
+        return Some(Response::BadRequest(
+            json!({ "error": "User already in group!" }),
+        ));
+    }
+
+    match get_relationship(&user, &member) {
+        Relationship::Friend => {
+            if database::get_collection("channels")
+                .update_one(
+                    doc! { "_id": &target.id },
+                    doc! {
+                        "$push": {
+                            "recipients": &member.id
+                        }
+                    },
+                    None,
+                )
+                .is_ok()
+            {
+                Some(Response::Result(super::Status::Ok))
+            } else {
+                Some(Response::InternalServerError(
+                    json!({ "error": "Failed to add user to group." }),
+                ))
+            }
+        }
+        _ => Some(Response::BadRequest(
+            json!({ "error": "Not friends with user." }),
+        )),
+    }
+}
+
+/// [groups] remove user from channel
+#[delete("/<target>/recipients/<member>")]
+pub fn remove_member(user: UserRef, target: ChannelRef, member: UserRef) -> Option<Response> {
+    if target.channel_type != 1 {
+        return Some(Response::BadRequest(json!({ "error": "Not a group DM." })));
+    }
+
+    if &user.id == &member.id {
+        return Some(Response::BadRequest(
+            json!({ "error": "Cannot kick yourself, leave the channel instead." }),
+        ));
+    }
+
+    let permissions = with_permissions!(user, target);
+
+    if !permissions.get_kick_members() {
+        return Some(Response::LackingPermission(Permission::KickMembers));
+    }
+
+    let set = vec_to_set(&target.recipients.unwrap());
+    if set.get(&member.id).is_none() {
+        return Some(Response::BadRequest(
+            json!({ "error": "User not in group!" }),
+        ));
+    }
+
+    if database::get_collection("channels")
+        .update_one(
+            doc! { "_id": &target.id },
+            doc! {
+                "$pull": {
+                    "recipients": &member.id
+                }
+            },
+            None,
+        )
+        .is_ok()
+    {
+        Some(Response::Result(super::Status::Ok))
+    } else {
+        Some(Response::InternalServerError(
+            json!({ "error": "Failed to add user to group." }),
+        ))
+    }
+}
+
 /// delete channel
 /// or leave group DM
 /// or close DM conversation
@@ -297,7 +414,11 @@ pub fn send_message(
     let nonce: String = message.nonce.chars().take(32).collect();
 
     let col = database::get_collection("messages");
-    if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() {
+    if col
+        .find_one(doc! { "nonce": nonce.clone() }, None)
+        .unwrap()
+        .is_some()
+    {
         return Some(Response::BadRequest(
             json!({ "error": "Message already sent!" }),
         ));
@@ -309,10 +430,10 @@ pub fn send_message(
             .insert_one(
                 doc! {
                     "_id": id.clone(),
-                    "nonce": nonce.clone(),
+                    "nonce": nonce,
                     "channel": target.id.clone(),
-                    "author": user.id.clone(),
-                    "content": content.clone(),
+                    "author": user.id,
+                    "content": content,
                 },
                 None,
             )
@@ -321,7 +442,7 @@ pub fn send_message(
             if target.channel_type == ChannelType::DM as u8 {
                 let col = database::get_collection("channels");
                 col.update_one(
-                    doc! { "_id": target.id.clone() },
+                    doc! { "_id": &target.id },
                     doc! { "$set": { "active": true } },
                     None,
                 )
@@ -420,7 +541,7 @@ pub fn edit_message(
         doc! {
             "$set": {
                 "content": edit.content.clone(),
-                "edited": UtcDatetime(edited.clone())
+                "edited": UtcDatetime(edited)
             },
             "$push": {
                 "previous_content": {
@@ -467,7 +588,7 @@ pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Op
 
     let col = database::get_collection("messages");
 
-    match col.delete_one(doc! { "_id": message.id.clone() }, None) {
+    match col.delete_one(doc! { "_id": &message.id }, None) {
         Ok(_) => {
             /*websocket::queue_message(
                 get_recipients(&target),
diff --git a/src/routes/guild.rs b/src/routes/guild.rs
index daadfee..42a8d9f 100644
--- a/src/routes/guild.rs
+++ b/src/routes/guild.rs
@@ -127,7 +127,11 @@ pub fn create_guild(user: UserRef, info: Json<CreateGuild>) -> Response {
 
     let channels = database::get_collection("channels");
     let col = database::get_collection("guilds");
-    if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() {
+    if col
+        .find_one(doc! { "nonce": nonce.clone() }, None)
+        .unwrap()
+        .is_some()
+    {
         return Response::BadRequest(json!({ "error": "Guild already created!" }));
     }
 
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index daee208..5ac65b0 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -89,6 +89,8 @@ pub fn mount(rocket: Rocket) -> Rocket {
             routes![
                 channel::create_group,
                 channel::channel,
+                channel::add_member,
+                channel::remove_member,
                 channel::delete,
                 channel::messages,
                 channel::get_message,
diff --git a/src/util/mod.rs b/src/util/mod.rs
index fe40409..c2f6c42 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -1,6 +1,6 @@
 use hashbrown::HashSet;
 use std::iter::FromIterator;
 
-pub fn vec_to_set<T: Clone + Eq + std::hash::Hash>(data: &Vec<T>) -> HashSet<T> {
+pub fn vec_to_set<T: Clone + Eq + std::hash::Hash>(data: &[T]) -> HashSet<T> {
     HashSet::from_iter(data.iter().cloned())
 }
-- 
GitLab