diff --git a/src/database/channel.rs b/src/database/channel.rs index 21f4f85b9715d7837cf0824798ee5b0308b611ec..5f84510f376c85e51c7be8f61ab235cddc164320 100644 --- a/src/database/channel.rs +++ b/src/database/channel.rs @@ -4,6 +4,7 @@ use lru::LruCache; use mongodb::bson::{doc, from_bson, Bson}; use rocket::http::RawStr; use rocket::request::FromParam; +use rocket_contrib::json::JsonValue; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; @@ -40,6 +41,40 @@ pub struct Channel { pub description: Option<String>, } +impl Channel { + pub fn serialise(self) -> JsonValue { + match self.channel_type { + 0 => json!({ + "id": self.id, + "type": self.channel_type, + "last_message": self.last_message, + "recipients": self.recipients, + }), + 1 => { + json!({ + "id": self.id, + "type": self.channel_type, + "last_message": self.last_message, + "recipients": self.recipients, + "name": self.name, + "owner": self.owner, + "description": self.description, + }) + } + 2 => { + json!({ + "id": self.id, + "type": self.channel_type, + "guild": self.guild, + "name": self.name, + "description": self.description, + }) + } + _ => unreachable!(), + } + } +} + lazy_static! { static ref CACHE: Arc<Mutex<LruCache<String, Channel>>> = Arc::new(Mutex::new(LruCache::new(4_000_000))); @@ -83,13 +118,13 @@ pub fn fetch_channels(ids: &Vec<String>) -> Result<Vec<Channel>, String> { { if let Ok(mut cache) = CACHE.lock() { - for gid in ids { - let existing = cache.get(gid); + for id in ids { + let existing = cache.get(id); if let Some(channel) = existing { channels.push((*channel).clone()); } else { - missing.push(gid); + missing.push(id); } } } else { diff --git a/src/database/guild.rs b/src/database/guild.rs index 0ba3f6bb70222f95e6247a93be7340f9e794fe35..b197cf3e173ef5ca0cecd1f74808665f7acc5fd5 100644 --- a/src/database/guild.rs +++ b/src/database/guild.rs @@ -4,6 +4,7 @@ use lru::LruCache; use mongodb::bson::{doc, from_bson, Bson}; use rocket::http::RawStr; use rocket::request::FromParam; +use rocket_contrib::json::JsonValue; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; @@ -49,6 +50,32 @@ pub struct Guild { pub default_permissions: u32, } +impl Guild { + pub fn serialise(self) -> JsonValue { + json!({ + "id": self.id, + "name": self.name, + "description": self.description, + "owner": self.owner + }) + } + + pub fn fetch_channels(&self) -> Result<Vec<super::channel::Channel>, String> { + super::channel::fetch_channels(&self.channels) + } + + pub fn seralise_with_channels(self) -> Result<JsonValue, String> { + let channels = self.fetch_channels()? + .into_iter() + .map(|x| x.serialise()) + .collect(); + + let mut value = self.serialise(); + value.as_object_mut().unwrap().insert("channels".to_string(), channels); + Ok(value) + } +} + #[derive(Hash, Eq, PartialEq)] pub struct MemberKey(pub String, pub String); @@ -91,6 +118,52 @@ pub fn fetch_guild(id: &str) -> Result<Option<Guild>, String> { } } +pub fn fetch_guilds(ids: &Vec<String>) -> Result<Vec<Guild>, String> { + let mut missing = vec![]; + let mut guilds = vec![]; + + { + if let Ok(mut cache) = CACHE.lock() { + for id in ids { + let existing = cache.get(id); + + if let Some(guild) = existing { + guilds.push((*guild).clone()); + } else { + missing.push(id); + } + } + } else { + return Err("Failed to lock cache.".to_string()); + } + } + + if missing.len() == 0 { + return Ok(guilds); + } + + let col = get_collection("guilds"); + if let Ok(result) = col.find(doc! { "_id": { "$in": missing } }, None) { + for item in result { + let mut cache = CACHE.lock().unwrap(); + if let Ok(doc) = item { + if let Ok(guild) = from_bson(Bson::Document(doc)) as Result<Guild, _> { + cache.put(guild.id.clone(), guild.clone()); + guilds.push(guild); + } else { + return Err("Failed to deserialize guild!".to_string()); + } + } else { + return Err("Failed to fetch guild.".to_string()); + } + } + + Ok(guilds) + } else { + Err("Failed to fetch channel from database.".to_string()) + } +} + pub fn fetch_member(key: MemberKey) -> Result<Option<Member>, String> { { if let Ok(mut cache) = MEMBER_CACHE.lock() { diff --git a/src/database/user.rs b/src/database/user.rs index 286613cf0b5bbde9a8da415ef2a4b23a0d363e93..8def8e61254c0859c960ac4507ffb2ed1458999f 100644 --- a/src/database/user.rs +++ b/src/database/user.rs @@ -1,9 +1,13 @@ use super::get_collection; +use super::guild::{Guild, fetch_guilds}; +use super::channel::{Channel, fetch_channels}; use lru::LruCache; use mongodb::bson::{doc, from_bson, Bson, DateTime}; +use mongodb::options::FindOptions; use rocket::http::{RawStr, Status}; use rocket::request::{self, FromParam, FromRequest, Request}; +use rocket_contrib::json::JsonValue; use rocket::Outcome; use std::sync::{Arc, Mutex}; use serde::{Deserialize, Serialize}; @@ -36,6 +40,116 @@ pub struct User { pub relations: Option<Vec<UserRelationship>>, } +impl User { + pub fn serialise(self, relationship: i32) -> JsonValue { + if relationship == super::Relationship::SELF as i32 { + json!({ + "id": self.id, + "username": self.username, + "display_name": self.display_name, + "email": self.email, + "verified": self.email_verification.verified, + }) + } else { + json!({ + "id": self.id, + "username": self.username, + "display_name": self.display_name, + "relationship": relationship + }) + } + } + + pub fn find_guilds(&self) -> Result<Vec<String>, String> { + let members = get_collection("members") + .find( + doc! { + "_id.user": &self.id + }, + None + ).map_err(|_| "Failed to fetch members.")?; + + Ok(members.into_iter() + .filter_map(|x| match x { + Ok(doc) => { + match doc.get_document("_id") { + Ok(id) => { + match id.get_str("guild") { + Ok(value) => Some(value.to_string()), + Err(_) => None + } + } + Err(_) => None + } + } + Err(_) => None + }) + .collect()) + } + + pub fn find_dms(&self) -> Result<Vec<String>, String> { + let channels = get_collection("channels") + .find( + doc! { + "recipients": &self.id + }, + FindOptions::builder() + .projection(doc! { "_id": 1 }) + .build() + ).map_err(|_| "Failed to fetch channel ids.")?; + + Ok(channels.into_iter() + .filter_map(|x| x.ok()) + .filter_map(|x| { + match x.get_str("_id") { + Ok(value) => Some(value.to_string()), + Err(_) => None + } + }) + .collect()) + } + + pub fn create_payload(self) -> Result<JsonValue, String> { + let v = vec![]; + let relations = self.relations.as_ref().unwrap_or(&v); + + let users: Vec<JsonValue> = fetch_users( + &relations + .iter() + .map(|x| x.id.clone()) + .collect() + )? + .into_iter() + .map(|x| { + let id = x.id.clone(); + x.serialise( + relations.iter() + .find(|y| y.id == id) + .unwrap() + .status as i32 + ) + }) + .collect(); + + let channels: Vec<JsonValue> = fetch_channels(&self.find_dms()?)? + .into_iter() + .map(|x| x.serialise()) + .collect(); + + let guilds: Vec<JsonValue> = fetch_guilds(&self.find_guilds()?)? + .into_iter() + .map(|x| x.serialise()) + .collect(); + + Ok(json!({ + "users": users, + "channels": channels, + "guilds": guilds, + "user": self.serialise(super::Relationship::SELF as i32) + })) + } +} + lazy_static! { static ref CACHE: Arc<Mutex<LruCache<String, User>>> = Arc::new(Mutex::new(LruCache::new(4_000_000))); @@ -73,6 +187,52 @@ pub fn fetch_user(id: &str) -> Result<Option<User>, String> { } } +pub fn fetch_users(ids: &Vec<String>) -> Result<Vec<User>, String> { + let mut missing = vec![]; + let mut users = vec![]; + + { + if let Ok(mut cache) = CACHE.lock() { + for id in ids { + let existing = cache.get(id); + + if let Some(user) = existing { + users.push((*user).clone()); + } else { + missing.push(id); + } + } + } else { + return Err("Failed to lock cache.".to_string()); + } + } + + if missing.len() == 0 { + return Ok(users); + } + + let col = get_collection("users"); + if let Ok(result) = col.find(doc! { "_id": { "$in": missing } }, None) { + for item in result { + let mut cache = CACHE.lock().unwrap(); + if let Ok(doc) = item { + if let Ok(user) = from_bson(Bson::Document(doc)) as Result<User, _> { + cache.put(user.id.clone(), user.clone()); + users.push(user); + } else { + return Err("Failed to deserialize user!".to_string()); + } + } else { + return Err("Failed to fetch user.".to_string()); + } + } + + Ok(users) + } else { + Err("Failed to fetch user from database.".to_string()) + } +} + #[derive(Debug)] pub enum AuthError { Failed, diff --git a/src/notifications/ws.rs b/src/notifications/ws.rs index 55ae2f5dcf6893476547b34680289d8a2edb876c..ba37ec9a44fc0ac1f57eabde07914e537ecc9b46 100644 --- a/src/notifications/ws.rs +++ b/src/notifications/ws.rs @@ -37,6 +37,7 @@ impl Handler for Server { match state.try_authenticate(self.id.clone(), token.to_string()) { StateResult::Success(user_id) => { let user = crate::database::user::fetch_user(&user_id).unwrap().unwrap(); + self.user_id = Some(user_id); self.sender.send( json!({ @@ -46,14 +47,10 @@ impl Handler for Server { .to_string(), )?; - self.user_id = Some(user_id); self.sender.send( json!({ "type": "ready", - "data": { - // ! FIXME: rewrite - "user": user, - } + "data": user.create_payload() }) .to_string(), ) diff --git a/src/routes/channel.rs b/src/routes/channel.rs index cfa631eebecbe1300ea0ef36bc74bf0f44f1a00e..57343394f918c63c092de8a9de5d4c63784fea4f 100644 --- a/src/routes/channel.rs +++ b/src/routes/channel.rs @@ -129,36 +129,7 @@ pub fn create_group(user: User, info: Json<CreateGroup>) -> Response { #[get("/<target>")] pub fn channel(user: User, target: Channel) -> Option<Response> { with_permissions!(user, target); - - match target.channel_type { - 0 => Some(Response::Success(json!({ - "id": target.id, - "type": target.channel_type, - "last_message": target.last_message, - "recipients": target.recipients, - }))), - 1 => { - Some(Response::Success(json!({ - "id": target.id, - "type": target.channel_type, - "last_message": target.last_message, - "recipients": target.recipients, - "name": target.name, - "owner": target.owner, - "description": target.description, - }))) - } - 2 => { - Some(Response::Success(json!({ - "id": target.id, - "type": target.channel_type, - "guild": target.guild, - "name": target.name, - "description": target.description, - }))) - } - _ => unreachable!(), - } + Some(Response::Success(target.serialise())) } /// [groups] add user to channel diff --git a/src/routes/guild.rs b/src/routes/guild.rs index 517a36548a39be98f084b654d868f1bb03b0712c..1ce5392b286e5cd4de93d2abbf7b195447a998e2 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, fetch_channels}, channel::Channel, Permission, PermissionCalculator, user::User + self, channel::fetch_channel, guild::fetch_guilds, channel::Channel, Permission, PermissionCalculator, user::User }; use crate::notifications::{ self, @@ -17,6 +17,7 @@ use rocket_contrib::json::Json; use serde::{Deserialize, Serialize}; use ulid::Ulid; +// ! FIXME: GET RID OF THIS macro_rules! with_permissions { ($user: expr, $target: expr) => {{ let permissions = PermissionCalculator::new($user.clone()) @@ -35,53 +36,34 @@ macro_rules! with_permissions { /// fetch your guilds #[get("/@me")] pub fn my_guilds(user: User) -> Response { - 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! { - "_id": { - "$in": guilds - } - }, - 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(), - })); + if let Ok(gids) = user.find_guilds() { + if let Ok(guilds) = fetch_guilds(&gids) { + let cids: Vec<String> = guilds + .iter() + .flat_map(|x| x.channels.clone()) + .collect(); + + if let Ok(channels) = database::channel::fetch_channels(&cids) { + let data: Vec<rocket_contrib::json::JsonValue> = guilds + .into_iter() + .map(|x| { + let id = x.id.clone(); + let mut obj = x.serialise(); + obj.as_object_mut().unwrap().insert( + "channels".to_string(), + channels.iter() + .filter(|x| x.guild.is_some() && x.guild.as_ref().unwrap() == &id) + .map(|x| x.clone().serialise()) + .collect() + ); + obj + }) + .collect(); + + Response::Success(json!(data)) + } else { + Response::InternalServerError(json!({ "error": "Failed to fetch channels." })) } - - Response::Success(json!(parsed)) } else { Response::InternalServerError(json!({ "error": "Failed to fetch guilds." })) } @@ -94,29 +76,11 @@ pub fn my_guilds(user: User) -> Response { #[get("/<target>")] pub fn guild(user: User, target: Guild) -> Option<Response> { with_permissions!(user, target); - - match fetch_channels(&target.channels) { - Ok(results) => { - let mut channels = vec![]; - for item in results { - channels.push(json!({ - "id": item.id, - "name": item.name, - "description": item.description, - })); - } - - Some(Response::Success(json!({ - "id": target.id, - "name": target.name, - "description": target.description, - "owner": target.owner, - "channels": channels, - }))) - } - Err(_) => Some(Response::InternalServerError( - json!({ "error": "Failed to fetch channels." }), - )) + + if let Ok(result) = target.seralise_with_channels() { + Some(Response::Success(result)) + } else { + Some(Response::InternalServerError(json!({ "error": "Failed to fetch channels!" }))) } } diff --git a/src/routes/user.rs b/src/routes/user.rs index ebe8dcf4cbc1c5232bc1a52de42a2adda6704106..145a61b445de833e08a58eb02c245e92122335ca 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -15,29 +15,18 @@ use ulid::Ulid; /// retrieve your user information #[get("/@me")] pub fn me(user: User) -> Response { - Response::Success(json!({ - "id": user.id, - "username": user.username, - "display_name": user.display_name, - "email": user.email, - "verified": user.email_verification.verified, - })) + Response::Success( + user.serialise(Relationship::SELF as i32) + ) } /// retrieve another user's information #[get("/<target>")] pub fn user(user: User, target: User) -> Response { - Response::Success(json!({ - "id": target.id, - "username": target.username, - "display_name": target.display_name, - "relationship": get_relationship(&user, &target) as i32, - "mutual": { - "guilds": mutual::find_mutual_guilds(&user.id, &target.id), - "friends": mutual::find_mutual_friends(&user.id, &target.id), - "groups": mutual::find_mutual_groups(&user.id, &target.id), - } - })) + let relationship = get_relationship(&user, &target) as i32; + Response::Success( + user.serialise(relationship) + ) } #[derive(Serialize, Deserialize)]