From 8956400e44a4217257d45610996ef39115671729 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Mon, 10 Aug 2020 18:53:24 +0200
Subject: [PATCH] Add caching for members, rewrite part of perms.

---
 src/database/channel.rs     |   4 +-
 src/database/guild.rs       | 116 +++++++++++++++++++++++----
 src/database/permissions.rs |  46 +++++------
 src/guards/guild.rs         | 153 ------------------------------------
 src/notifications/mod.rs    |   2 +-
 src/routes/guild.rs         |  42 ++++++----
 6 files changed, 154 insertions(+), 209 deletions(-)
 delete mode 100644 src/guards/guild.rs

diff --git a/src/database/channel.rs b/src/database/channel.rs
index a7a8b3e..2ec7822 100644
--- a/src/database/channel.rs
+++ b/src/database/channel.rs
@@ -179,8 +179,8 @@ pub fn process_event(event: &Notification) {
                     owner: None,
                     guild: Some(ev.id.clone()),
                     name: Some(ev.name.clone()),
-                    description: Some(ev.description.clone())
-                }
+                    description: Some(ev.description.clone()),
+                },
             );
         }
         Notification::guild_channel_delete(ev) => {
diff --git a/src/database/guild.rs b/src/database/guild.rs
index b5968ad..7f1e763 100644
--- a/src/database/guild.rs
+++ b/src/database/guild.rs
@@ -48,9 +48,14 @@ pub struct Guild {
     pub default_permissions: u32,
 }
 
+#[derive(Hash, Eq, PartialEq)]
+pub struct MemberKey(pub String, pub String);
+
 lazy_static! {
     static ref CACHE: Arc<Mutex<LruCache<String, Guild>>> =
         Arc::new(Mutex::new(LruCache::new(4_000_000)));
+    static ref MEMBER_CACHE: Arc<Mutex<LruCache<MemberKey, Member>>> =
+        Arc::new(Mutex::new(LruCache::new(4_000_000)));
 }
 
 pub fn fetch_guild(id: &str) -> Result<Option<Guild>, String> {
@@ -85,37 +90,57 @@ pub fn fetch_guild(id: &str) -> Result<Option<Guild>, String> {
     }
 }
 
-impl<'r> FromParam<'r> for Guild {
-    type Error = &'r RawStr;
+pub fn fetch_member(key: MemberKey) -> Result<Option<Member>, String> {
+    {
+        if let Ok(mut cache) = MEMBER_CACHE.lock() {
+            let existing = cache.get(&key);
 
-    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
-        if let Ok(result) = fetch_guild(param) {
-            if let Some(channel) = result {
-                Ok(channel)
-            } else {
-                Err(param)
+            if let Some(member) = existing {
+                return Ok(Some((*member).clone()));
             }
         } else {
-            Err(param)
+            return Err("Failed to lock cache.".to_string());
         }
     }
-}
 
-pub fn get_member(guild_id: &String, member: &String) -> Option<Member> {
-    if let Ok(result) = get_collection("members").find_one(
+    let col = get_collection("members");
+    if let Ok(result) = col.find_one(
         doc! {
-            "_id.guild": &guild_id,
-            "_id.user": &member,
+            "_id.guild": &key.0,
+            "_id.user": &key.1,
         },
         None,
     ) {
         if let Some(doc) = result {
-            Some(from_bson(Bson::Document(doc)).expect("Failed to unwrap member."))
+            if let Ok(member) = from_bson(Bson::Document(doc)) as Result<Member, _> {
+                let mut cache = MEMBER_CACHE.lock().unwrap();
+                cache.put(key, member.clone());
+
+                Ok(Some(member))
+            } else {
+                Err("Failed to deserialize member!".to_string())
+            }
         } else {
-            None
+            Ok(None)
         }
     } else {
-        None
+        Err("Failed to fetch member from database.".to_string())
+    }
+}
+
+impl<'r> FromParam<'r> for Guild {
+    type Error = &'r RawStr;
+
+    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
+        if let Ok(result) = fetch_guild(param) {
+            if let Some(channel) = result {
+                Ok(channel)
+            } else {
+                Err(param)
+            }
+        } else {
+            Err(param)
+        }
     }
 }
 
@@ -176,3 +201,60 @@ pub fn get_invite<U: Into<Option<String>>>(
         None
     }
 }
+
+use crate::notifications::events::Notification;
+
+pub fn process_event(event: &Notification) {
+    match event {
+        Notification::guild_channel_create(ev) => {} // ? for later use
+        Notification::guild_channel_create(ev) => {} // ? for later use
+        Notification::guild_delete(ev) => {}
+        Notification::guild_user_join(ev) => {}
+        Notification::guild_user_leave(ev) => {}
+        /*Notification::group_user_join(ev) => {
+            let mut cache = CACHE.lock().unwrap();
+            let entry = cache.pop(&ev.id);
+
+            if entry.is_some() {
+                let mut channel = entry.unwrap();
+                channel.recipients.as_mut().unwrap().push(ev.user.clone());
+                cache.put(ev.id.clone(), channel);
+            }
+        }
+        Notification::group_user_leave(ev) => {
+            let mut cache = CACHE.lock().unwrap();
+            let entry = cache.pop(&ev.id);
+
+            if entry.is_some() {
+                let mut channel = entry.unwrap();
+                let recipients = channel.recipients.as_mut().unwrap();
+                if let Some(pos) = recipients.iter().position(|x| *x == ev.user) {
+                    recipients.remove(pos);
+                }
+                cache.put(ev.id.clone(), channel);
+            }
+        }
+        Notification::guild_channel_create(ev) => {
+            let mut cache = CACHE.lock().unwrap();
+            cache.put(
+                ev.id.clone(),
+                Channel {
+                    id: ev.channel.clone(),
+                    channel_type: 2,
+                    active: None,
+                    last_message: None,
+                    recipients: None,
+                    owner: None,
+                    guild: Some(ev.id.clone()),
+                    name: Some(ev.name.clone()),
+                    description: Some(ev.description.clone())
+                }
+            );
+        }
+        Notification::guild_channel_delete(ev) => {
+            let mut cache = CACHE.lock().unwrap();
+            cache.pop(&ev.channel);
+        }*/
+        _ => {}
+    }
+}
diff --git a/src/database/permissions.rs b/src/database/permissions.rs
index 7c553e0..37d35f7 100644
--- a/src/database/permissions.rs
+++ b/src/database/permissions.rs
@@ -1,6 +1,6 @@
 use super::mutual::has_mutual_connection;
 use crate::database::channel::Channel;
-use crate::database::guild::{fetch_guild, get_member, Guild, Member};
+use crate::database::guild::{fetch_guild, fetch_member, Guild, Member, MemberKey};
 use crate::database::user::UserRelationship;
 use crate::guards::auth::UserRef;
 
@@ -140,7 +140,9 @@ impl PermissionCalculator {
         };
 
         if let Some(guild) = &guild {
-            self.member = get_member(&guild.id, &self.user.id);
+            if let Ok(result) = fetch_member(MemberKey(guild.id.clone(), self.user.id.clone())) {
+                self.member = result;
+            }
         }
 
         self.guild = guild;
@@ -164,32 +166,32 @@ impl PermissionCalculator {
             match channel.channel_type {
                 0 => {
                     if let Some(arr) = &channel.recipients {
-                        let mut other_user = "";
+                        let mut other_user = None;
                         for item in arr {
                             if item != &self.user.id {
-                                other_user = item;
+                                other_user = Some(item);
                             }
                         }
 
-                        // ? In this case, it is a "self DM".
-                        if other_user == "" {
-                            return 1024 + 128 + 32 + 16 + 1;
-                        }
-
-                        let relationships = self.user.fetch_relationships();
-                        let relationship =
-                            get_relationship_internal(&self.user.id, &other_user, &relationships);
-
-                        if relationship == Relationship::Friend {
-                            permissions = 1024 + 128 + 32 + 16 + 1;
-                        } else if relationship == Relationship::Blocked
-                            || relationship == Relationship::BlockedOther
-                        {
-                            permissions = 1;
-                        } else if has_mutual_connection(&self.user.id, other_user, true) {
-                            permissions = 1024 + 128 + 32 + 16 + 1;
+                        if let Some(other) = other_user {
+                            let relationships = self.user.fetch_relationships();
+                            let relationship =
+                                get_relationship_internal(&self.user.id, &other, &relationships);
+    
+                            if relationship == Relationship::Friend {
+                                permissions = 1024 + 128 + 32 + 16 + 1;
+                            } else if relationship == Relationship::Blocked
+                                || relationship == Relationship::BlockedOther
+                            {
+                                permissions = 1;
+                            } else if has_mutual_connection(&self.user.id, other, true) {
+                                permissions = 1024 + 128 + 32 + 16 + 1;
+                            } else {
+                                permissions = 1;
+                            }
                         } else {
-                            permissions = 1;
+                            // ? In this case, it is a "self DM".
+                            return 1024 + 128 + 32 + 16 + 1;
                         }
                     }
                 }
diff --git a/src/guards/guild.rs b/src/guards/guild.rs
deleted file mode 100644
index 51e3707..0000000
--- a/src/guards/guild.rs
+++ /dev/null
@@ -1,153 +0,0 @@
-use mongodb::bson::{doc, from_bson, Bson, Document};
-use mongodb::options::FindOneOptions;
-use rocket::http::RawStr;
-use rocket::request::FromParam;
-use serde::{Deserialize, Serialize};
-
-use crate::database;
-use crate::database::guild::{Ban, Invite, Member};
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct GuildRef {
-    #[serde(rename = "_id")]
-    pub id: String,
-    pub name: String,
-    pub description: String,
-    pub owner: String,
-
-    pub bans: Vec<Ban>,
-
-    pub default_permissions: i32,
-}
-
-impl GuildRef {
-    pub fn from(id: String) -> Option<GuildRef> {
-        match database::get_collection("guilds").find_one(
-            doc! { "_id": id },
-            FindOneOptions::builder()
-                .projection(doc! {
-                    "name": 1,
-                    "description": 1,
-                    "owner": 1,
-                    "bans": 1,
-                    "default_permissions": 1
-                })
-                .build(),
-        ) {
-            Ok(result) => match result {
-                Some(doc) => {
-                    Some(from_bson(mongodb::bson::mongodb::bson::Document(doc)).expect("Failed to unwrap guild."))
-                }
-                None => None,
-            },
-            Err(_) => None,
-        }
-    }
-
-    pub fn fetch_data(&self, projection: Document) -> Option<Document> {
-        database::get_collection("guilds")
-            .find_one(
-                doc! { "_id": &self.id },
-                FindOneOptions::builder().projection(projection).build(),
-            )
-            .expect("Failed to fetch guild from database.")
-    }
-
-    pub fn fetch_data_given(&self, mut filter: Document, projection: Document) -> Option<Document> {
-        filter.insert("_id", self.id.clone());
-        database::get_collection("guilds")
-            .find_one(
-                filter,
-                FindOneOptions::builder().projection(projection).build(),
-            )
-            .expect("Failed to fetch guild from database.")
-    }
-}
-
-impl<'r> FromParam<'r> for GuildRef {
-    type Error = &'r RawStr;
-
-    fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
-        if let Some(guild) = GuildRef::from(param.to_string()) {
-            Ok(guild)
-        } else {
-            Err(param)
-        }
-    }
-}
-
-pub fn get_member(guild_id: &String, member: &String) -> Option<Member> {
-    if let Ok(result) = database::get_collection("members").find_one(
-        doc! {
-            "_id.guild": &guild_id,
-            "_id.user": &member,
-        },
-        None,
-    ) {
-        if let Some(doc) = result {
-            Some(from_bson(mongodb::bson::Document(doc)).expect("Failed to unwrap member."))
-        } else {
-            None
-        }
-    } else {
-        None
-    }
-}
-
-pub fn get_invite<U: Into<Option<String>>>(
-    code: &String,
-    user: U,
-) -> Option<(String, String, Invite)> {
-    let mut doc = doc! {
-        "invites": {
-            "$elemMatch": {
-                "code": &code
-            }
-        }
-    };
-
-    if let Some(user_id) = user.into() {
-        doc.insert(
-            "bans",
-            doc! {
-                "$not": {
-                    "$elemMatch": {
-                        "id": user_id
-                    }
-                }
-            },
-        );
-    }
-
-    if let Ok(result) = database::get_collection("guilds").find_one(
-        doc,
-        FindOneOptions::builder()
-            .projection(doc! {
-                "_id": 1,
-                "name": 1,
-                "invites.$": 1,
-            })
-            .build(),
-    ) {
-        if let Some(doc) = result {
-            let invite = doc
-                .get_array("invites")
-                .unwrap()
-                .iter()
-                .next()
-                .unwrap()
-                .as_document()
-                .unwrap();
-
-            Some((
-                doc.get_str("_id").unwrap().to_string(),
-                doc.get_str("name").unwrap().to_string(),
-                from_bson(mongodb::bson::Document(invite.clone())).unwrap(),
-            ))
-        } else {
-            None
-        }
-    } else {
-        None
-    }
-}
diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs
index 897dec0..abc7723 100644
--- a/src/notifications/mod.rs
+++ b/src/notifications/mod.rs
@@ -18,7 +18,7 @@ pub fn send_message<U: Into<Option<Vec<String>>>, G: Into<Option<String>>>(
     let guild = guild.into();
 
     data.push_to_cache();
-    
+
     if pubsub::send_message(users.clone(), guild.clone(), data.clone()) {
         state::send_message(users, guild, data.serialize());
 
diff --git a/src/routes/guild.rs b/src/routes/guild.rs
index 52f5b4a..7726943 100644
--- a/src/routes/guild.rs
+++ b/src/routes/guild.rs
@@ -1,6 +1,6 @@
 use super::channel::ChannelType;
 use super::Response;
-use crate::database::guild::{get_invite, get_member, Guild};
+use crate::database::guild::{fetch_member as get_member, get_invite, Guild, MemberKey};
 use crate::database::{
     self, channel::fetch_channel, channel::Channel, Permission, PermissionCalculator,
 };
@@ -641,11 +641,17 @@ pub fn fetch_members(user: UserRef, target: Guild) -> Option<Response> {
 pub fn fetch_member(user: UserRef, target: Guild, other: String) -> Option<Response> {
     with_permissions!(user, target);
 
-    if let Some(member) = get_member(&target.id, &other) {
-        Some(Response::Success(json!({
-            "id": member.id.user,
-            "nickname": member.nickname,
-        })))
+    if let Ok(result) = get_member(MemberKey(target.id, user.id)) {
+        if let Some(member) = result {
+            Some(Response::Success(json!({
+                "id": member.id.user,
+                "nickname": member.nickname,
+            })))
+        } else {
+            Some(Response::NotFound(
+                json!({ "error": "Member does not exist!" }),
+            ))
+        }
     } else {
         Some(Response::InternalServerError(
             json!({ "error": "Failed to fetch member or user does not exist." }),
@@ -668,10 +674,14 @@ pub fn kick_member(user: UserRef, target: Guild, other: String) -> Option<Respon
         return Some(Response::LackingPermission(Permission::KickMembers));
     }
 
-    if get_member(&target.id, &other).is_none() {
-        return Some(Response::BadRequest(
-            json!({ "error": "User not part of guild." }),
-        ));
+    if let Ok(result) = get_member(MemberKey( target.id.clone(), other.clone() )) {
+        if result.is_none() {
+            return Some(Response::BadRequest(
+                json!({ "error": "User not part of guild." }),
+            ));
+        }
+    } else {
+        return Some(Response::InternalServerError(json!({ "error": "Failed to fetch member." })))
     }
 
     if database::get_collection("members")
@@ -734,10 +744,14 @@ pub fn ban_member(
         return Some(Response::LackingPermission(Permission::BanMembers));
     }
 
-    if get_member(&target.id, &other).is_none() {
-        return Some(Response::BadRequest(
-            json!({ "error": "User not part of guild." }),
-        ));
+    if let Ok(result) = get_member(MemberKey( target.id.clone(), other.clone() )) {
+        if result.is_none() {
+            return Some(Response::BadRequest(
+                json!({ "error": "User not part of guild." }),
+            ));
+        }
+    } else {
+        return Some(Response::InternalServerError(json!({ "error": "Failed to fetch member." })))
     }
 
     if database::get_collection("guilds")
-- 
GitLab