From 82f6e6215f491565b86c76909ac3b0364fbacf61 Mon Sep 17 00:00:00 2001
From: Paul Makles <paulmakles@gmail.com>
Date: Thu, 9 Apr 2020 11:38:32 +0100
Subject: [PATCH] Implement channel permissions.

---
 src/database/guild.rs       |   6 +-
 src/database/mod.rs         |   1 +
 src/database/mutual.rs      |  35 +++++
 src/database/permissions.rs | 141 +++++++++++++++----
 src/guards/auth.rs          |  66 +++++----
 src/routes/channel.rs       | 267 +++++++++++++++++++-----------------
 src/routes/mod.rs           |  20 +++
 src/routes/user.rs          |  64 ++-------
 8 files changed, 360 insertions(+), 240 deletions(-)
 create mode 100644 src/database/mutual.rs

diff --git a/src/database/guild.rs b/src/database/guild.rs
index cdbdef2..5279061 100644
--- a/src/database/guild.rs
+++ b/src/database/guild.rs
@@ -8,7 +8,7 @@ pub fn find_member_permissions<C: Into<Option<String>>>(
     id: String,
     guild: String,
     channel: C,
-) -> u8 {
+) -> u32 {
     let col = get_collection("guilds");
 
     match col.find_one(
@@ -31,10 +31,10 @@ pub fn find_member_permissions<C: Into<Option<String>>>(
         Ok(result) => {
             if let Some(doc) = result {
                 if doc.get_str("owner").unwrap() == id {
-                    return u8::MAX;
+                    return u32::MAX;
                 }
 
-                doc.get_i32("default_permissions").unwrap() as u8
+                doc.get_i32("default_permissions").unwrap() as u32
             } else {
                 0
             }
diff --git a/src/database/mod.rs b/src/database/mod.rs
index ecc10c5..a5e7741 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -27,6 +27,7 @@ pub fn get_collection(collection: &str) -> Collection {
 pub mod channel;
 pub mod guild;
 pub mod message;
+pub mod mutual;
 pub mod permissions;
 pub mod user;
 
diff --git a/src/database/mutual.rs b/src/database/mutual.rs
new file mode 100644
index 0000000..aad8036
--- /dev/null
+++ b/src/database/mutual.rs
@@ -0,0 +1,35 @@
+use super::get_collection;
+
+use bson::{bson, doc};
+use mongodb::options::FindOneOptions;
+
+/*pub struct MutualGuild {
+
+}
+
+pub fn find_mutual_guilds(user_id: String, target_id: String) -> Vec<> {
+
+}*/
+
+pub fn has_mutual_connection(user_id: String, target_id: String) -> bool {
+    let col = get_collection("guilds");
+    if let Ok(result) = col.find_one(
+        doc! {
+            "$and": [
+                { "members": { "$elemMatch": { "id": user_id   } } },
+                { "members": { "$elemMatch": { "id": target_id } } },
+            ]
+        },
+        FindOneOptions::builder()
+            .projection(doc! { "_id": 1 })
+            .build(),
+    ) {
+        if result.is_some() {
+            true
+        } else {
+            false
+        }
+    } else {
+        false
+    }
+}
diff --git a/src/database/permissions.rs b/src/database/permissions.rs
index 48f62b7..1a31abf 100644
--- a/src/database/permissions.rs
+++ b/src/database/permissions.rs
@@ -1,31 +1,99 @@
+use super::mutual::has_mutual_connection;
+use crate::database::user::UserRelationship;
+use crate::guards::auth::UserRef;
+use crate::guards::channel::ChannelRef;
+use crate::guards::guild::GuildRef;
+
+use bson::{bson, doc};
+use num_enum::TryFromPrimitive;
+
+#[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
+#[repr(u8)]
+pub enum Relationship {
+    FRIEND = 0,
+    OUTGOING = 1,
+    INCOMING = 2,
+    BLOCKED = 3,
+    BLOCKEDOTHER = 4,
+    NONE = 5,
+    SELF = 6,
+}
+
+#[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
+#[repr(u32)]
+pub enum Permission {
+    ACCESS = 1,
+    CREATE_INVITE = 2,
+    KICK_MEMBERS = 4,
+    BAN_MEMBERS = 8,
+    READ_MESSAGES = 16,
+    SEND_MESSAGES = 32,
+    MANAGE_MESSAGES = 64,
+    MANAGE_CHANNELS = 128,
+    MANAGE_SERVER = 256,
+    MANAGE_ROLES = 512,
+}
+
 bitfield! {
-    pub struct MemberPermissions(MSB0 [u8]);
+    pub struct MemberPermissions(MSB0 [u32]);
     u8;
-    pub get_access, set_access: 7;
-    pub get_create_invite, set_create_invite: 6;
-    pub get_kick_members, set_kick_members: 5;
-    pub get_ban_members, set_ban_members: 4;
-    pub get_read_messages, set_read_messages: 3;
-    pub get_send_messages, set_send_messages: 2;
+    pub get_access, set_access: 31;
+    pub get_create_invite, set_create_invite: 30;
+    pub get_kick_members, set_kick_members: 29;
+    pub get_ban_members, set_ban_members: 28;
+    pub get_read_messages, set_read_messages: 27;
+    pub get_send_messages, set_send_messages: 26;
+    pub get_manage_messages, set_manage_messages: 25;
+    pub get_manage_channels, set_manage_channels: 24;
+    pub get_manage_server, set_manage_server: 23;
+    pub get_manage_roles, set_manage_roles: 22;
 }
 
-use super::get_collection;
-use crate::guards::channel::ChannelRef;
-use crate::guards::guild::GuildRef;
+pub fn get_relationship_internal(
+    user_id: &str,
+    target_id: &str,
+    relationships: &Option<Vec<UserRelationship>>,
+) -> Relationship {
+    if user_id == target_id {
+        return Relationship::SELF;
+    }
 
-use bson::{bson, doc};
-use mongodb::options::FindOneOptions;
+    if let Some(arr) = &relationships {
+        for entry in arr {
+            if entry.id == target_id {
+                match entry.status {
+                    0 => return Relationship::FRIEND,
+                    1 => return Relationship::OUTGOING,
+                    2 => return Relationship::INCOMING,
+                    3 => return Relationship::BLOCKED,
+                    4 => return Relationship::BLOCKEDOTHER,
+                    _ => return Relationship::NONE,
+                }
+            }
+        }
+    }
+
+    Relationship::NONE
+}
+
+pub fn get_relationship(a: &UserRef, b: &UserRef) -> Relationship {
+    if a.id == b.id {
+        return Relationship::SELF;
+    }
+
+    get_relationship_internal(&a.id, &b.id, &a.fetch_relationships())
+}
 
 pub struct PermissionCalculator {
-    pub user_id: String,
+    pub user: UserRef,
     pub channel: Option<ChannelRef>,
     pub guild: Option<GuildRef>,
 }
 
 impl PermissionCalculator {
-    pub fn new(user_id: String) -> PermissionCalculator {
+    pub fn new(user: UserRef) -> PermissionCalculator {
         PermissionCalculator {
-            user_id,
+            user,
             channel: None,
             guild: None,
         }
@@ -45,7 +113,7 @@ impl PermissionCalculator {
         }
     }
 
-    pub fn calculate(self) -> u8 {
+    pub fn calculate(self) -> u32 {
         let guild = if let Some(value) = self.guild {
             Some(value)
         } else if let Some(channel) = &self.channel {
@@ -70,14 +138,14 @@ impl PermissionCalculator {
                 doc! {
                     "members": {
                         "$elemMatch": {
-                            "id": &self.user_id,
+                            "id": &self.user.id,
                         }
                     }
                 },
-                doc! { }
+                doc! {},
             ) {
-                if guild.owner == self.user_id {
-                    return u8::MAX;
+                if guild.owner == self.user.id {
+                    return u32::MAX;
                 }
 
                 permissions = guild.default_permissions;
@@ -87,29 +155,42 @@ impl PermissionCalculator {
         if let Some(channel) = &self.channel {
             match channel.channel_type {
                 0 => {
+                    // ? check user is part of the channel
                     if let Some(arr) = &channel.recipients {
+                        let mut other_user = "";
                         for item in arr {
-                            if item == &self.user_id {
+                            if item == &self.user.id {
                                 permissions = 49;
-                                break;
+                            } else {
+                                other_user = item;
                             }
                         }
+
+                        let relationships = self.user.fetch_relationships();
+                        let relationship =
+                            get_relationship_internal(&self.user.id, &other_user, &relationships);
+
+                        if relationship == Relationship::BLOCKED
+                            || relationship == Relationship::BLOCKEDOTHER
+                        {
+                            permissions = 1;
+                        } else if has_mutual_connection(self.user.id, other_user.to_string()) {
+                            permissions = 49;
+                        }
                     }
-                },
-                1 => {
-                    unreachable!()
-                },
+                }
+                1 => unreachable!(),
                 2 => {
                     // nothing implemented yet
-                },
+                }
                 _ => {}
             }
         }
 
-        permissions as u8
+        permissions as u32
     }
 
-    pub fn as_permission(self) -> MemberPermissions<[u8; 1]> {
-        MemberPermissions([ self.calculate() ])
+    pub fn as_permission(self) -> MemberPermissions<[u32; 1]> {
+        MemberPermissions([self.calculate()])
     }
 }
diff --git a/src/guards/auth.rs b/src/guards/auth.rs
index 66df8ea..0d30470 100644
--- a/src/guards/auth.rs
+++ b/src/guards/auth.rs
@@ -1,14 +1,14 @@
+use bson::{bson, doc, from_bson, Document};
+use mongodb::options::FindOneOptions;
 use rocket::http::{RawStr, Status};
 use rocket::request::{self, FromParam, FromRequest, Request};
 use rocket::Outcome;
-use bson::{bson, doc, from_bson, Document};
 use serde::{Deserialize, Serialize};
-use mongodb::options::FindOneOptions;
 
 use crate::database;
 use database::user::{User, UserRelationship};
 
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct UserRef {
     pub id: String,
     pub username: String,
@@ -29,11 +29,13 @@ impl UserRef {
         let user = database::get_collection("users")
             .find_one(
                 doc! { "_id": &self.id },
-                FindOneOptions::builder().projection(doc! { "relations": 1 }).build(),
+                FindOneOptions::builder()
+                    .projection(doc! { "relations": 1 })
+                    .build(),
             )
             .expect("Failed to fetch user relationships from database.")
             .expect("Missing user document.");
-        
+
         if let Ok(arr) = user.get_array("relations") {
             let mut relationships = vec![];
             for item in arr {
@@ -77,13 +79,15 @@ impl<'a, 'r> FromRequest<'a, 'r> for UserRef {
                     .unwrap();
 
                 if let Some(user) = result {
-                    Outcome::Success(
-                        UserRef {
-                            id: user.get_str("_id").unwrap().to_string(),
-                            username: user.get_str("username").unwrap().to_string(),
-                            email_verified: user.get_document("email_verification").unwrap().get_bool("verified").unwrap(),
-                        }
-                    )
+                    Outcome::Success(UserRef {
+                        id: user.get_str("_id").unwrap().to_string(),
+                        username: user.get_str("username").unwrap().to_string(),
+                        email_verified: user
+                            .get_document("email_verification")
+                            .unwrap()
+                            .get_bool("verified")
+                            .unwrap(),
+                    })
                 } else {
                     Outcome::Failure((Status::Forbidden, AuthError::Invalid))
                 }
@@ -124,26 +128,28 @@ impl<'r> FromParam<'r> for UserRef {
     fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
         let col = database::get_db().collection("users");
         let result = database::get_collection("users")
-                    .find_one(
-                        doc! { "_id": param.to_string() },
-                        FindOneOptions::builder()
-                            .projection(doc! {
-                                "_id": 1,
-                                "username": 1,
-                                "email_verification.verified": 1,
-                            })
-                            .build(),
-                    )
-                    .unwrap();
+            .find_one(
+                doc! { "_id": param.to_string() },
+                FindOneOptions::builder()
+                    .projection(doc! {
+                        "_id": 1,
+                        "username": 1,
+                        "email_verification.verified": 1,
+                    })
+                    .build(),
+            )
+            .unwrap();
 
         if let Some(user) = result {
-            Ok(
-                UserRef {
-                    id: user.get_str("_id").unwrap().to_string(),
-                    username: user.get_str("username").unwrap().to_string(),
-                    email_verified: user.get_document("email_verification").unwrap().get_bool("verified").unwrap(),
-                }
-            )
+            Ok(UserRef {
+                id: user.get_str("_id").unwrap().to_string(),
+                username: user.get_str("username").unwrap().to_string(),
+                email_verified: user
+                    .get_document("email_verification")
+                    .unwrap()
+                    .get_bool("verified")
+                    .unwrap(),
+            })
         } else {
             Err(param)
         }
diff --git a/src/routes/channel.rs b/src/routes/channel.rs
index 5dc57be..5c92f3f 100644
--- a/src/routes/channel.rs
+++ b/src/routes/channel.rs
@@ -1,8 +1,7 @@
 use super::Response;
-use crate::database::{self, channel::Channel, message::Message, user::User, PermissionCalculator};
-use crate::guards::channel::ChannelRef;
+use crate::database::{self, message::Message, Permission, PermissionCalculator};
 use crate::guards::auth::UserRef;
-use crate::websocket;
+use crate::guards::channel::ChannelRef;
 
 use bson::{bson, doc, from_bson, Bson::UtcDatetime};
 use chrono::prelude::*;
@@ -20,19 +19,17 @@ pub enum ChannelType {
 }
 
 macro_rules! with_permissions {
-    ($user: expr, $target: expr) => {
-        {
-            let permissions = PermissionCalculator::new($user.id.clone())
-                .channel($target.clone())
-                .as_permission();
-            
-            if !permissions.get_access() {
-                return None;
-            }
+    ($user: expr, $target: expr) => {{
+        let permissions = PermissionCalculator::new($user.clone())
+            .channel($target.clone())
+            .as_permission();
 
-            permissions
+        if !permissions.get_access() {
+            return None;
         }
-    };
+
+        permissions
+    }};
 }
 
 /// fetch channel information
@@ -41,34 +38,28 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
     with_permissions!(user, target);
 
     match target.channel_type {
-        0..=1 => Some(Response::Success(
-            json!({
-                "id": target.id,
-                "type": target.channel_type,
-                "recipients": target.recipients,
-            })
-        )),
+        0..=1 => Some(Response::Success(json!({
+            "id": target.id,
+            "type": target.channel_type,
+            "recipients": target.recipients,
+        }))),
         2 => {
-            if let Some(info) = target.fetch_data(
-                doc! {
-                    "name": 1,
-                    "description": 1,
-                }
-            ) {
-                Some(Response::Success(
-                    json!({
-                        "id": target.id,
-                        "type": target.channel_type,
-                        "guild": target.guild,
-                        "name": info.get_str("name").unwrap(),
-                        "description": info.get_str("description").unwrap_or(""),
-                    })
-                ))
+            if let Some(info) = target.fetch_data(doc! {
+                "name": 1,
+                "description": 1,
+            }) {
+                Some(Response::Success(json!({
+                    "id": target.id,
+                    "type": target.channel_type,
+                    "guild": target.guild,
+                    "name": info.get_str("name").unwrap(),
+                    "description": info.get_str("description").unwrap_or(""),
+                })))
             } else {
                 None
             }
-        },
-        _ => unreachable!()
+        }
+        _ => unreachable!(),
     }
 }
 
@@ -77,38 +68,49 @@ pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
 /// or close DM conversation
 #[delete("/<target>")]
 pub fn delete(user: UserRef, target: ChannelRef) -> Option<Response> {
-    with_permissions!(user, target);
+    let permissions = with_permissions!(user, target);
+
+    if !permissions.get_manage_channels() {
+        return Some(Response::LackingPermission(Permission::MANAGE_CHANNELS));
+    }
 
     let col = database::get_collection("channels");
-    Some(match target.channel_type {
+    match target.channel_type {
         0 => {
-            col.update_one(
+            if col.update_one(
                 doc! { "_id": target.id },
                 doc! { "$set": { "active": false } },
                 None,
-            )
-            .expect("Failed to update channel.");
-
-            Response::Result(super::Status::Ok)
+            ).is_ok() {
+                Some(Response::Result(super::Status::Ok))
+            } else {
+                Some(Response::InternalServerError(json!({ "error": "Failed to close channel." })))
+            }
         }
         1 => {
             // ? TODO: group dm
 
-            Response::Result(super::Status::Ok)
+            Some(Response::Result(super::Status::Ok))
         }
         2 => {
             // ? TODO: guild
 
-            Response::Result(super::Status::Ok)
+            Some(Response::Result(super::Status::Ok))
         }
-        _ => Response::InternalServerError(json!({ "error": "Unknown error has occurred." })),
-    })
+        _ => Some(Response::InternalServerError(
+            json!({ "error": "Unknown error has occurred." }),
+        )),
+    }
 }
 
 /// fetch channel messages
 #[get("/<target>/messages")]
 pub fn messages(user: UserRef, target: ChannelRef) -> Option<Response> {
-    with_permissions!(user, target);
+    let permissions = with_permissions!(user, target);
+
+    if !permissions.get_read_messages() {
+        return Some(Response::LackingPermission(Permission::READ_MESSAGES));
+    }
 
     let col = database::get_collection("messages");
     let result = col.find(doc! { "channel": target.id }, None).unwrap();
@@ -136,8 +138,16 @@ pub struct SendMessage {
 
 /// send a message to a channel
 #[post("/<target>/messages", data = "<message>")]
-pub fn send_message(user: UserRef, target: ChannelRef, message: Json<SendMessage>) -> Option<Response> {
-    with_permissions!(user, target);
+pub fn send_message(
+    user: UserRef,
+    target: ChannelRef,
+    message: Json<SendMessage>,
+) -> Option<Response> {
+    let permissions = with_permissions!(user, target);
+
+    if !permissions.get_send_messages() {
+        return Some(Response::LackingPermission(Permission::SEND_MESSAGES));
+    }
 
     let content: String = message.content.chars().take(2000).collect();
     let nonce: String = message.nonce.chars().take(32).collect();
@@ -201,7 +211,11 @@ pub fn send_message(user: UserRef, target: ChannelRef, message: Json<SendMessage
 /// get a message
 #[get("/<target>/messages/<message>")]
 pub fn get_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
-    with_permissions!(user, target);
+    let permissions = with_permissions!(user, target);
+
+    if !permissions.get_read_messages() {
+        return Some(Response::LackingPermission(Permission::READ_MESSAGES));
+    }
 
     let prev =
         // ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES
@@ -243,87 +257,90 @@ pub fn edit_message(
 ) -> Option<Response> {
     with_permissions!(user, target);
 
-    Some(if message.author != user.id {
-        Response::Unauthorized(json!({ "error": "You did not send this message." }))
-    } else {
-        let col = database::get_collection("messages");
+    if message.author != user.id {
+        return Some(Response::Unauthorized(
+            json!({ "error": "You did not send this message." }),
+        ));
+    }
 
-        let time = if let Some(edited) = message.edited {
-            edited.0
-        } else {
-            Ulid::from_string(&message.id).unwrap().datetime()
-        };
+    let col = database::get_collection("messages");
+    let time = if let Some(edited) = message.edited {
+        edited.0
+    } else {
+        Ulid::from_string(&message.id).unwrap().datetime()
+    };
 
-        let edited = Utc::now();
-        match col.update_one(
-            doc! { "_id": message.id.clone() },
-            doc! {
-                "$set": {
-                    "content": edit.content.clone(),
-                    "edited": UtcDatetime(edited.clone())
-                },
-                "$push": {
-                    "previous_content": {
-                        "content": message.content,
-                        "time": time,
-                    }
-                },
+    let edited = Utc::now();
+    match col.update_one(
+        doc! { "_id": message.id.clone() },
+        doc! {
+            "$set": {
+                "content": edit.content.clone(),
+                "edited": UtcDatetime(edited.clone())
             },
-            None,
-        ) {
-            Ok(_) => {
-                /*websocket::queue_message(
-                    get_recipients(&target),
-                    json!({
-                        "type": "message_update",
-                        "data": {
-                            "id": message.id,
-                            "channel": target.id,
-                            "content": edit.content.clone(),
-                            "edited": edited.timestamp()
-                        },
-                    })
-                    .to_string(),
-                );*/
-
-                Response::Result(super::Status::Ok)
-            }
-            Err(_) => {
-                Response::InternalServerError(json!({ "error": "Failed to update message." }))
-            }
+            "$push": {
+                "previous_content": {
+                    "content": message.content,
+                    "time": time,
+                }
+            },
+        },
+        None,
+    ) {
+        Ok(_) => {
+            /*websocket::queue_message(
+                get_recipients(&target),
+                json!({
+                    "type": "message_update",
+                    "data": {
+                        "id": message.id,
+                        "channel": target.id,
+                        "content": edit.content.clone(),
+                        "edited": edited.timestamp()
+                    },
+                })
+                .to_string(),
+            );*/
+
+            Some(Response::Result(super::Status::Ok))
         }
-    })
+        Err(_) => Some(Response::InternalServerError(
+            json!({ "error": "Failed to update message." }),
+        )),
+    }
 }
 
 /// delete a message
 #[delete("/<target>/messages/<message>")]
 pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
-    with_permissions!(user, target);
+    let permissions = with_permissions!(user, target);
 
-    Some(if message.author != user.id {
-        Response::Unauthorized(json!({ "error": "You did not send this message." }))
-    } else {
-        let col = database::get_collection("messages");
-
-        match col.delete_one(doc! { "_id": message.id.clone() }, None) {
-            Ok(_) => {
-                /*websocket::queue_message(
-                    get_recipients(&target),
-                    json!({
-                        "type": "message_delete",
-                        "data": {
-                            "id": message.id,
-                            "channel": target.id
-                        },
-                    })
-                    .to_string(),
-                );*/
-
-                Response::Result(super::Status::Ok)
-            }
-            Err(_) => {
-                Response::InternalServerError(json!({ "error": "Failed to delete message." }))
-            }
+    if !permissions.get_manage_messages() {
+        if message.author != user.id {
+            return Some(Response::LackingPermission(Permission::MANAGE_MESSAGES));
+        }
+    }
+
+    let col = database::get_collection("messages");
+
+    match col.delete_one(doc! { "_id": message.id.clone() }, None) {
+        Ok(_) => {
+            /*websocket::queue_message(
+                get_recipients(&target),
+                json!({
+                    "type": "message_delete",
+                    "data": {
+                        "id": message.id,
+                        "channel": target.id
+                    },
+                })
+                .to_string(),
+            );*/
+
+            Some(Response::Result(super::Status::Ok))
         }
-    })
+        Err(_) => Some(Response::InternalServerError(
+            json!({ "error": "Failed to delete message." }),
+        )),
+    }
 }
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index ce019e4..96a19ec 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -3,6 +3,8 @@ pub use rocket::response::Redirect;
 use rocket::Rocket;
 use rocket_contrib::json::JsonValue;
 
+use crate::database::Permission;
+
 pub mod account;
 pub mod channel;
 pub mod guild;
@@ -21,6 +23,8 @@ pub enum Response {
     BadRequest(JsonValue),
     #[response(status = 401)]
     Unauthorized(JsonValue),
+    #[response(status = 401)]
+    LackingPermission(Permission),
     #[response(status = 404)]
     NotFound(JsonValue),
     #[response(status = 406)]
@@ -37,6 +41,22 @@ pub enum Response {
     InternalServerError(JsonValue),
 }
 
+use rocket::http::ContentType;
+use rocket::request::Request;
+use std::io::Cursor;
+
+impl<'a> rocket::response::Responder<'a> for Permission {
+    fn respond_to(self, _: &Request) -> rocket::response::Result<'a> {
+        rocket::response::Response::build()
+            .header(ContentType::JSON)
+            .sized_body(Cursor::new(format!(
+                "{{\"error\":\"Lacking {:?} permission.\"}}",
+                self
+            )))
+            .ok()
+    }
+}
+
 pub fn mount(rocket: Rocket) -> Rocket {
     rocket
         .mount("/api", routes![root::root])
diff --git a/src/routes/user.rs b/src/routes/user.rs
index 01dcc3f..713043f 100644
--- a/src/routes/user.rs
+++ b/src/routes/user.rs
@@ -1,7 +1,8 @@
 use super::Response;
-use crate::database::{self, channel::Channel, user::UserRelationship};
-use crate::routes::channel;
+use crate::database::{self, channel::Channel};
+use crate::database::{get_relationship, get_relationship_internal, Relationship};
 use crate::guards::auth::UserRef;
+use crate::routes::channel;
 
 use bson::{bson, doc, from_bson};
 use mongodb::options::FindOptions;
@@ -9,61 +10,20 @@ use rocket_contrib::json::Json;
 use serde::{Deserialize, Serialize};
 use ulid::Ulid;
 
-enum Relationship {
-    FRIEND = 0,
-    OUTGOING = 1,
-    INCOMING = 2,
-    BLOCKED = 3,
-    BLOCKEDOTHER = 4,
-    NONE = 5,
-    SELF = 6,
-}
-
-fn get_relationship_internal(user_id: &str, target_id: &str, relationships: &Option<Vec<UserRelationship>>) -> Relationship {
-    if user_id == target_id {
-        return Relationship::SELF;
-    }
-
-    if let Some(arr) = &relationships {
-        for entry in arr {
-            if entry.id == target_id {
-                match entry.status {
-                    0 => return Relationship::FRIEND,
-                    1 => return Relationship::OUTGOING,
-                    2 => return Relationship::INCOMING,
-                    3 => return Relationship::BLOCKED,
-                    4 => return Relationship::BLOCKEDOTHER,
-                    _ => return Relationship::NONE,
-                }
-            }
-        }
-    }
-
-    Relationship::NONE
-}
-
-fn get_relationship(a: &UserRef, b: &UserRef) -> Relationship {
-    if a.id == b.id {
-        return Relationship::SELF;
-    }
-
-    get_relationship_internal(&a.id, &b.id, &a.fetch_relationships())
-}
-
 /// retrieve your user information
 #[get("/@me")]
 pub fn me(user: UserRef) -> Response {
     if let Some(info) = user.fetch_data(doc! { "email": 1 }) {
-        Response::Success(
-            json!({
-                "id": user.id,
-                "username": user.username,
-                "email": info.get_str("email").unwrap(),
-                "verified": user.email_verified,
-            })
-        )
+        Response::Success(json!({
+            "id": user.id,
+            "username": user.username,
+            "email": info.get_str("email").unwrap(),
+            "verified": user.email_verified,
+        }))
     } else {
-        Response::InternalServerError(json!({ "error": "Failed to fetch information from database." }))
+        Response::InternalServerError(
+            json!({ "error": "Failed to fetch information from database." }),
+        )
     }
 }
 
-- 
GitLab