From 74b4238f0439fd30ed285049c5462f9799cbe67e Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Wed, 12 Aug 2020 11:45:05 +0200
Subject: [PATCH] Add channels field to guild object.

---
 Cargo.lock                         |  2 +-
 Cargo.toml                         |  2 +-
 src/database/channel.rs            |  6 +--
 src/database/guild.rs              |  2 +-
 src/database/migrations/scripts.rs | 57 ++++++++++++++++++++++-
 src/database/mod.rs                |  1 -
 src/routes/channel.rs              | 18 +-------
 src/routes/guild.rs                | 74 +++++++++++++++++-------------
 src/routes/root.rs                 |  4 +-
 9 files changed, 107 insertions(+), 59 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 842ac0d..04236fb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1907,7 +1907,7 @@ dependencies = [
 
 [[package]]
 name = "revolt"
-version = "0.2.7"
+version = "0.2.8"
 dependencies = [
  "bcrypt",
  "bitfield",
diff --git a/Cargo.toml b/Cargo.toml
index 0a7a76a..207a9c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "revolt"
-version = "0.2.7"
+version = "0.2.8"
 authors = ["Paul Makles <paulmakles@gmail.com>"]
 edition = "2018"
 
diff --git a/src/database/channel.rs b/src/database/channel.rs
index 6bad1b8..21f4f85 100644
--- a/src/database/channel.rs
+++ b/src/database/channel.rs
@@ -77,7 +77,7 @@ pub fn fetch_channel(id: &str) -> Result<Option<Channel>, String> {
     }
 }
 
-pub fn fetch_channels(ids: &Vec<String>) -> Result<Option<Vec<Channel>>, String> {
+pub fn fetch_channels(ids: &Vec<String>) -> Result<Vec<Channel>, String> {
     let mut missing = vec![];
     let mut channels = vec![];
 
@@ -98,7 +98,7 @@ pub fn fetch_channels(ids: &Vec<String>) -> Result<Option<Vec<Channel>>, String>
     }
 
     if missing.len() == 0 {
-        return Ok(Some(channels));
+        return Ok(channels);
     }
 
     let col = get_collection("channels");
@@ -117,7 +117,7 @@ pub fn fetch_channels(ids: &Vec<String>) -> Result<Option<Vec<Channel>>, String>
             }
         }
 
-        Ok(Some(channels))
+        Ok(channels)
     } else {
         Err("Failed to fetch channel from database.".to_string())
     }
diff --git a/src/database/guild.rs b/src/database/guild.rs
index 73a2698..0ba3f6b 100644
--- a/src/database/guild.rs
+++ b/src/database/guild.rs
@@ -42,7 +42,7 @@ pub struct Guild {
     pub description: String,
     pub owner: String,
 
-    // ? FIXME: ADD: pub channels: Vec<Channel>,
+    pub channels: Vec<String>,
     pub invites: Vec<Invite>,
     pub bans: Vec<Ban>,
 
diff --git a/src/database/migrations/scripts.rs b/src/database/migrations/scripts.rs
index 72291af..c2041ea 100644
--- a/src/database/migrations/scripts.rs
+++ b/src/database/migrations/scripts.rs
@@ -2,6 +2,7 @@ use super::super::get_collection;
 
 use serde::{Serialize, Deserialize};
 use mongodb::bson::{Bson, from_bson, doc};
+use mongodb::options::FindOptions;
 use log::info;
 
 #[derive(Serialize, Deserialize)]
@@ -10,7 +11,7 @@ struct MigrationInfo {
     revision: i32
 }
 
-pub const LATEST_REVISION: i32 = 1;
+pub const LATEST_REVISION: i32 = 2;
 
 pub fn migrate_database() {
     let migrations = get_collection("migrations");
@@ -48,6 +49,60 @@ pub fn run_migrations(revision: i32) -> i32 {
         info!("Running migration [revision 0]: Test migration system.");
     }
 
+    if revision <= 1 {
+        info!("Running migration [revision 1]: Add channels to guild object.");
+
+        let col = get_collection("guilds");
+        let guilds = col.find(
+            None,
+            FindOptions::builder()
+                .projection(doc! { "_id": 1 })
+                .build()
+        )
+            .expect("Failed to fetch guilds.");
+        
+        let result = get_collection("channels").find(
+            doc! {
+                "type": 2
+            },
+            FindOptions::builder()
+                .projection(doc! { "_id": 1, "guild": 1 })
+                .build()
+        ).expect("Failed to fetch channels.");
+
+        let mut channels = vec![];
+        for doc in result {
+            let channel = doc.expect("Failed to fetch channel.");
+            let id  = channel.get_str("_id").expect("Failed to get channel id.").to_string();
+            let gid = channel.get_str("guild").expect("Failed to get guild id.").to_string();
+
+            channels.push(( id, gid ));
+        }
+        
+        for doc in guilds {
+            let guild = doc.expect("Failed to fetch guild.");
+            let id = guild.get_str("_id").expect("Failed to get guild id.");
+
+            let list: Vec<String> = channels
+                .iter()
+                .filter(|x| x.1 == id)
+                .map(|x| x.0.clone())
+                .collect();
+            
+            col.update_one(
+                doc! {
+                    "_id": id
+                },
+                doc! {
+                    "$set": {
+                        "channels": list
+                    }
+                },
+                None
+            ).expect("Failed to update guild.");
+        }
+    }
+
     // Reminder to update LATEST_REVISION when adding new migrations.
     LATEST_REVISION
 }
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 0c5c534..d473952 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -1,4 +1,3 @@
-use mongodb::bson::doc;
 use mongodb::sync::{Client, Collection, Database};
 use std::env;
 
diff --git a/src/routes/channel.rs b/src/routes/channel.rs
index 5d79050..cfa631e 100644
--- a/src/routes/channel.rs
+++ b/src/routes/channel.rs
@@ -138,11 +138,6 @@ pub fn channel(user: User, target: Channel) -> Option<Response> {
             "recipients": target.recipients,
         }))),
         1 => {
-            /*if let Some(info) = target.fetch_data(doc! {
-                "name": 1,
-                "description": 1,
-                "owner": 1,
-            }) {*/
             Some(Response::Success(json!({
                 "id": target.id,
                 "type": target.channel_type,
@@ -152,15 +147,8 @@ pub fn channel(user: User, target: Channel) -> Option<Response> {
                 "owner": target.owner,
                 "description": target.description,
             })))
-            /*} else {
-                None
-            }*/
         }
         2 => {
-            /*if let Some(info) = target.fetch_data(doc! {
-                "name": 1,
-                "description": 1,
-            }) {*/
             Some(Response::Success(json!({
                 "id": target.id,
                 "type": target.channel_type,
@@ -168,9 +156,6 @@ pub fn channel(user: User, target: Channel) -> Option<Response> {
                 "name": target.name,
                 "description": target.description,
             })))
-            /*} else {
-                None
-            }*/
         }
         _ => unreachable!(),
     }
@@ -447,7 +432,8 @@ pub fn delete(user: User, target: Channel) -> Option<Response> {
                         "$pull": {
                             "invites": {
                                 "channel": &target.id
-                            }
+                            },
+                            "channels": &target.id
                         }
                     },
                     None,
diff --git a/src/routes/guild.rs b/src/routes/guild.rs
index 72d7031..517a365 100644
--- a/src/routes/guild.rs
+++ b/src/routes/guild.rs
@@ -2,7 +2,7 @@ use super::channel::ChannelType;
 use super::Response;
 use crate::database::guild::{fetch_member as get_member, get_invite, Guild, MemberKey};
 use crate::database::{
-    self, channel::fetch_channel, channel::Channel, Permission, PermissionCalculator, user::User
+    self, channel::{fetch_channel, fetch_channels}, channel::Channel, Permission, PermissionCalculator, user::User
 };
 use crate::notifications::{
     self,
@@ -10,7 +10,7 @@ use crate::notifications::{
 };
 use crate::util::gen_token;
 
-use mongodb::bson::{doc, from_bson, Bson};
+use mongodb::bson::{doc, Bson};
 use mongodb::options::{FindOneOptions, FindOptions};
 use rocket::request::Form;
 use rocket_contrib::json::Json;
@@ -95,26 +95,15 @@ pub fn my_guilds(user: User) -> Response {
 pub fn guild(user: User, target: Guild) -> Option<Response> {
     with_permissions!(user, target);
 
-    let col = database::get_collection("channels");
-    match col.find(
-        doc! {
-            "type": 2,
-            "guild": &target.id,
-        },
-        None,
-    ) {
+    match fetch_channels(&target.channels) {
         Ok(results) => {
             let mut channels = vec![];
             for item in results {
-                if let Ok(entry) = item {
-                    if let Ok(channel) = from_bson(Bson::Document(entry)) as Result<Channel, _> {
-                        channels.push(json!({
-                            "id": channel.id,
-                            "name": channel.name,
-                            "description": channel.description,
-                        }));
-                    }
-                }
+                channels.push(json!({
+                    "id": item.id,
+                    "name": item.name,
+                    "description": item.description,
+                }));
             }
 
             Some(Response::Success(json!({
@@ -127,7 +116,7 @@ pub fn guild(user: User, target: Guild) -> Option<Response> {
         }
         Err(_) => Some(Response::InternalServerError(
             json!({ "error": "Failed to fetch channels." }),
-        )),
+        ))
     }
 }
 
@@ -305,20 +294,39 @@ pub fn create_channel(user: User, target: Guild, info: Json<CreateChannel>) -> O
             )
             .is_ok()
         {
-            notifications::send_message_threaded(
-                None,
-                target.id.clone(),
-                Notification::guild_channel_create(ChannelCreate {
-                    id: target.id.clone(),
-                    channel: id.clone(),
-                    name: name.clone(),
-                    description: description.clone(),
-                }),
-            );
-
-            Some(Response::Success(json!({ "id": &id })))
+            if database::get_collection("guilds")
+                .update_one(
+                    doc! {
+                        "_id": &target.id
+                    },
+                    doc! {
+                        "$addToSet": {
+                            "channels": &id
+                        }
+                    },
+                    None
+                )
+                .is_ok()
+            {
+                notifications::send_message_threaded(
+                    None,
+                    target.id.clone(),
+                    Notification::guild_channel_create(ChannelCreate {
+                        id: target.id.clone(),
+                        channel: id.clone(),
+                        name: name.clone(),
+                        description: description.clone(),
+                    }),
+                );
+
+                Some(Response::Success(json!({ "id": &id })))
+            } else {
+                Some(Response::InternalServerError(
+                    json!({ "error": "Couldn't save channel list." }),
+                ))
+            }
         } else {
-            Some(Response::BadRequest(
+            Some(Response::InternalServerError(
                 json!({ "error": "Couldn't create channel." }),
             ))
         }
diff --git a/src/routes/root.rs b/src/routes/root.rs
index bb9fa55..269cb55 100644
--- a/src/routes/root.rs
+++ b/src/routes/root.rs
@@ -6,11 +6,11 @@ use mongodb::bson::doc;
 #[get("/")]
 pub fn root() -> Response {
     Response::Success(json!({
-        "revolt": "0.2.7",
+        "revolt": "0.2.8",
         "version": {
             "major": 0,
             "minor": 2,
-            "patch": 7
+            "patch": 8
         }
     }))
 }
-- 
GitLab