From b6772143bc048d99a220d150493fb93bf0f0fa27 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Fri, 10 Apr 2020 21:06:46 +0100
Subject: [PATCH] Migrate guild members to its own collection, +rout

---
 src/database/guild.rs       |   1 -
 src/database/mutual.rs      |   6 +-
 src/database/permissions.rs |  24 ++---
 src/routes/account.rs       |  23 +++--
 src/routes/guild.rs         | 186 ++++++++++++++++++++++++++++++------
 src/routes/mod.rs           |   9 +-
 6 files changed, 192 insertions(+), 57 deletions(-)

diff --git a/src/database/guild.rs b/src/database/guild.rs
index 7a4155b..b2ff3b5 100644
--- a/src/database/guild.rs
+++ b/src/database/guild.rs
@@ -24,7 +24,6 @@ pub struct Guild {
     pub owner: String,
 
     pub channels: Vec<String>,
-    pub members: Vec<Member>,
     pub invites: Vec<Invite>,
 
     pub default_permissions: u32,
diff --git a/src/database/mutual.rs b/src/database/mutual.rs
index 9fd3ed1..5eb2341 100644
--- a/src/database/mutual.rs
+++ b/src/database/mutual.rs
@@ -4,12 +4,12 @@ use bson::doc;
 use mongodb::options::{FindOneOptions, FindOptions};
 
 pub fn find_mutual_guilds(user_id: &str, target_id: &str) -> Vec<String> {
-    let col = get_collection("guilds");
+    let col = get_collection("members");
     if let Ok(result) = col.find(
         doc! {
             "$and": [
-                { "members": { "$elemMatch": { "id": user_id   } } },
-                { "members": { "$elemMatch": { "id": target_id } } },
+                { "id": user_id   },
+                { "id": target_id },
             ]
         },
         FindOptions::builder().projection(doc! { "_id": 1 }).build(),
diff --git a/src/database/permissions.rs b/src/database/permissions.rs
index 13d812a..02e6397 100644
--- a/src/database/permissions.rs
+++ b/src/database/permissions.rs
@@ -1,4 +1,5 @@
 use super::mutual::has_mutual_connection;
+use crate::database::get_collection;
 use crate::database::user::UserRelationship;
 use crate::guards::auth::UserRef;
 use crate::guards::channel::ChannelRef;
@@ -32,6 +33,7 @@ pub enum Permission {
     ManageChannels = 128,
     ManageServer = 256,
     ManageRoles = 512,
+    SendDirectMessages = 1024,
 }
 
 bitfield! {
@@ -47,6 +49,7 @@ bitfield! {
     pub get_manage_channels, set_manage_channels: 24;
     pub get_manage_server, set_manage_server: 23;
     pub get_manage_roles, set_manage_roles: 22;
+    pub get_send_direct_messages, set_send_direct_messages: 21;
 }
 
 pub fn get_relationship_internal(
@@ -134,21 +137,20 @@ impl PermissionCalculator {
 
         let mut permissions: u32 = 0;
         if let Some(guild) = guild {
-            if let Some(_data) = guild.fetch_data_given(
+            if let Ok(result) = get_collection("members").find_one(
                 doc! {
-                    "members": {
-                        "$elemMatch": {
-                            "id": &self.user.id,
-                        }
-                    }
+                    "_id.user": &self.user.id,
+                    "_id.guild": &guild.id,
                 },
-                doc! {},
+                None,
             ) {
-                if guild.owner == self.user.id {
-                    return u32::MAX;
-                }
+                if result.is_some() {
+                    if guild.owner == self.user.id {
+                        return u32::MAX;
+                    }
 
-                permissions = guild.default_permissions as u32;
+                    permissions = guild.default_permissions as u32;
+                }
             }
         }
 
diff --git a/src/routes/account.rs b/src/routes/account.rs
index c7a2e76..eb854d6 100644
--- a/src/routes/account.rs
+++ b/src/routes/account.rs
@@ -246,14 +246,19 @@ pub fn login(info: Json<Login>) -> Response {
                     Some(t) => t.to_string(),
                     None => {
                         let token = gen_token(92);
-                        if col.update_one(
-                            doc! { "_id": &user.id },
-                            doc! { "$set": { "access_token": token.clone() } },
-                            None,
-                        ).is_err() {
-                            return Response::InternalServerError(json!({ "error": "Failed database operation." }));
+                        if col
+                            .update_one(
+                                doc! { "_id": &user.id },
+                                doc! { "$set": { "access_token": token.clone() } },
+                                None,
+                            )
+                            .is_err()
+                        {
+                            return Response::InternalServerError(
+                                json!({ "error": "Failed database operation." }),
+                            );
                         }
-                        
+
                         token
                     }
                 };
@@ -277,9 +282,7 @@ pub struct Token {
 pub fn token(info: Json<Token>) -> Response {
     let col = database::get_collection("users");
 
-    if let Ok(result) = col
-        .find_one(doc! { "access_token": info.token.clone() }, None)
-    {
+    if let Ok(result) = col.find_one(doc! { "access_token": info.token.clone() }, None) {
         if let Some(user) = result {
             Response::Success(json!({
                 "id": user.get_str("_id").unwrap(),
diff --git a/src/routes/guild.rs b/src/routes/guild.rs
index edd92ed..142df3b 100644
--- a/src/routes/guild.rs
+++ b/src/routes/guild.rs
@@ -1,10 +1,11 @@
 use super::channel::ChannelType;
 use super::Response;
-use crate::database::{self, channel::Channel, PermissionCalculator};
+use crate::database::{self, channel::Channel, PermissionCalculator, Permission};
 use crate::guards::auth::UserRef;
 use crate::guards::guild::GuildRef;
 
 use bson::{doc, from_bson, Bson};
+use mongodb::options::FindOptions;
 use rocket_contrib::json::Json;
 use serde::{Deserialize, Serialize};
 use ulid::Ulid;
@@ -26,32 +27,59 @@ macro_rules! with_permissions {
 /// fetch your guilds
 #[get("/@me")]
 pub fn my_guilds(user: UserRef) -> Response {
-    let col = database::get_collection("guilds");
-    let guilds = col
-        .find(
+    if let Ok(result) = database::get_collection("members").find(
+        doc! {
+            "_id.user": &user.id
+        },
+        None,
+    ) {
+        let mut guilds = vec![];
+        for item in result {
+            if let Ok(entry) = item {
+                guilds.push(Bson::String(
+                    entry
+                        .get_document("_id")
+                        .unwrap()
+                        .get_str("guild")
+                        .unwrap()
+                        .to_string(),
+                ));
+            }
+        }
+
+        if let Ok(result) = database::get_collection("guilds").find(
             doc! {
-                "members": {
-                    "$elemMatch": {
-                        "id": user.id,
-                    }
+                "_id": {
+                    "$in": guilds
                 }
             },
-            None,
-        )
-        .unwrap();
-
-    let mut parsed = vec![];
-    for item in guilds {
-        let doc = item.unwrap();
-        parsed.push(json!({
-            "id": doc.get_str("_id").unwrap(),
-            "name": doc.get_str("name").unwrap(),
-            "description": doc.get_str("description").unwrap(),
-            "owner": doc.get_str("owner").unwrap(),
-        }));
-    }
+            FindOptions::builder()
+                .projection(doc! {
+                    "_id": 1,
+                    "name": 1,
+                    "description": 1,
+                    "owner": 1,
+                })
+                .build(),
+        ) {
+            let mut parsed = vec![];
+            for item in result {
+                let doc = item.unwrap();
+                parsed.push(json!({
+                    "id": doc.get_str("_id").unwrap(),
+                    "name": doc.get_str("name").unwrap(),
+                    "description": doc.get_str("description").unwrap(),
+                    "owner": doc.get_str("owner").unwrap(),
+                }));
+            }
 
-    Response::Success(json!(parsed))
+            Response::Success(json!(parsed))
+        } else {
+            Response::InternalServerError(json!({ "error": "Failed to fetch guilds." }))
+        }
+    } else {
+        Response::InternalServerError(json!({ "error": "Failed to fetch memberships." }))
+    }
 }
 
 /// fetch a guild
@@ -157,21 +185,33 @@ pub fn create_guild(user: UserRef, info: Json<CreateGuild>) -> Response {
         );
     }
 
+    if database::get_collection("members")
+        .insert_one(
+            doc! {
+                "_id": {
+                    "guild": &id,
+                    "user": &user.id
+                }
+            },
+            None,
+        )
+        .is_err()
+    {
+        return Response::InternalServerError(
+            json!({ "error": "Failed to add you to members list." }),
+        );
+    }
+
     if col
         .insert_one(
             doc! {
-                "_id": id.clone(),
+                "_id": &id,
                 "nonce": nonce,
                 "name": name,
                 "description": description,
-                "owner": user.id.clone(),
+                "owner": &user.id,
                 "channels": [
-                    channel_id.clone()
-                ],
-                "members": [
-                    {
-                        "id": user.id,
-                    }
+                    &channel_id
                 ],
                 "invites": [],
                 "default_permissions": 51,
@@ -189,3 +229,87 @@ pub fn create_guild(user: UserRef, info: Json<CreateGuild>) -> Response {
         Response::InternalServerError(json!({ "error": "Failed to create guild." }))
     }
 }
+
+/// fetch a guild's member
+#[get("/<target>/members")]
+pub fn fetch_members(user: UserRef, target: GuildRef) -> Option<Response> {
+    with_permissions!(user, target);
+
+    if let Ok(result) =
+        database::get_collection("members").find(doc! { "_id.guild": target.id }, None)
+    {
+        let mut users = vec![];
+
+        for item in result {
+            if let Ok(doc) = item {
+                users.push(json!({
+                    "id": doc.get_document("_id").unwrap().get_str("user").unwrap(),
+                    "nickname": doc.get_str("nickname").ok(),
+                }));
+            }
+        }
+
+        Some(Response::Success(json!(users)))
+    } else {
+        Some(Response::InternalServerError(
+            json!({ "error": "Failed to fetch members." }),
+        ))
+    }
+}
+
+/// fetch a guild member
+#[get("/<target>/members/<other>")]
+pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> {
+    with_permissions!(user, target);
+
+    if let Ok(result) = database::get_collection("members").find_one(
+        doc! {
+            "_id.guild": &target.id,
+            "_id.user": &other,
+        },
+        None,
+    ) {
+        if let Some(doc) = result {
+            Some(Response::Success(json!({
+                "id": doc.get_document("_id").unwrap().get_str("user").unwrap(),
+                "nickname": doc.get_str("nickname").ok(),
+            })))
+        } else {
+            Some(Response::NotFound(
+                json!({ "error": "User not part of guild." }),
+            ))
+        }
+    } else {
+        Some(Response::InternalServerError(
+            json!({ "error": "Failed to fetch member." }),
+        ))
+    }
+}
+
+/// kick a guild member
+#[delete("/<target>/members/<other>")]
+pub fn kick_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> {
+    let permissions = with_permissions!(user, target);
+
+    if !permissions.get_kick_members() {
+        return Some(Response::LackingPermission(Permission::KickMembers));
+    }
+
+    if user.id == other {
+        return Some(Response::BadRequest(json!({ "error": "Cannot kick yourself." })))
+    }
+
+    if database::get_collection("members").delete_one(
+        doc! {
+            "_id.guild": &target.id,
+            "_id.user": &other,
+        },
+        None,
+    ).is_ok() {
+        Some(Response::Result(super::Status::Ok))
+    } else {
+        Some(Response::InternalServerError(
+            json!({ "error": "Failed to kick member." }),
+        ))
+    }
+}
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index b6fc933..fe95c2d 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -103,6 +103,13 @@ pub fn mount(rocket: Rocket) -> Rocket {
         )
         .mount(
             "/api/guild",
-            routes![guild::my_guilds, guild::guild, guild::create_guild],
+            routes![
+                guild::my_guilds,
+                guild::guild,
+                guild::create_guild,
+                guild::fetch_members,
+                guild::fetch_member,
+                guild::kick_member
+            ],
         )
 }
-- 
GitLab