From 6a1912c27b36d908aacf98345aa76c1d47058b77 Mon Sep 17 00:00:00 2001 From: Paul Makles <paulmakles@gmail.com> Date: Mon, 3 Aug 2020 15:23:32 +0200 Subject: [PATCH] Cache guilds. --- src/database/channel.rs | 125 ++++++++++++++++++++++++------- src/database/guild.rs | 144 ++++++++++++++++++++++++++++++++++-- src/database/message.rs | 26 ++++++- src/database/permissions.rs | 13 ++-- src/guards/channel.rs | 118 ----------------------------- src/guards/mod.rs | 2 - src/routes/guild.rs | 120 +++++++++++++++--------------- 7 files changed, 327 insertions(+), 221 deletions(-) delete mode 100644 src/guards/channel.rs diff --git a/src/database/channel.rs b/src/database/channel.rs index 5da3f91..6649c2b 100644 --- a/src/database/channel.rs +++ b/src/database/channel.rs @@ -1,9 +1,11 @@ -use crate::database::get_collection; +use super::get_collection; use serde::{Deserialize, Serialize}; +use rocket::request::FromParam; use std::sync::{Arc, Mutex}; -use lru::LruCache; use bson::{doc, from_bson}; +use rocket::http::RawStr; +use lru::LruCache; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct LastMessage { @@ -42,6 +44,100 @@ lazy_static! { static ref CACHE: Arc<Mutex<LruCache<String, Channel>>> = Arc::new(Mutex::new(LruCache::new(4_000_000))); } +pub fn fetch_channel(id: &str) -> Result<Option<Channel>, String> { + { + if let Ok(mut cache) = CACHE.lock() { + let existing = cache.get(&id.to_string()); + + if let Some(channel) = existing { + return Ok(Some((*channel).clone())); + } + } else { + return Err("Failed to lock cache.".to_string()); + } + } + + let col = get_collection("channels"); + if let Ok(result) = col.find_one(doc! { "_id": id }, None) { + if let Some(doc) = result { + if let Ok(channel) = from_bson(bson::Bson::Document(doc)) as Result<Channel, _> { + let mut cache = CACHE.lock().unwrap(); + cache.put(id.to_string(), channel.clone()); + + Ok(Some(channel)) + } else { + Err("Failed to deserialize channel!".to_string()) + } + } else { + Ok(None) + } + } else { + Err("Failed to fetch channel from database.".to_string()) + } +} + +pub fn fetch_channels(ids: &Vec<String>) -> Result<Option<Vec<Channel>>, String> { + let mut missing = vec![]; + let mut channels = vec![]; + + { + if let Ok(mut cache) = CACHE.lock() { + for gid in ids { + let existing = cache.get(gid); + + if let Some(channel) = existing { + channels.push((*channel).clone()); + } else { + missing.push(gid); + } + } + } else { + return Err("Failed to lock cache.".to_string()); + } + } + + if missing.len() == 0 { + return Ok(Some(channels)) + } + + let col = get_collection("channels"); + 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(channel) = from_bson(bson::Bson::Document(doc)) as Result<Channel, _> { + cache.put(channel.id.clone(), channel.clone()); + channels.push(channel); + } else { + return Err("Failed to deserialize channel!".to_string()) + } + } else { + return Err("Failed to fetch channel.".to_string()); + } + } + + Ok(Some(channels)) + } else { + Err("Failed to fetch channel from database.".to_string()) + } +} + +impl<'r> FromParam<'r> for Channel { + type Error = &'r RawStr; + + fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { + if let Ok(result) = fetch_channel(param) { + if let Some(channel) = result { + Ok(channel) + } else { + Err(param) + } + } else { + Err(param) + } + } +} + /*pub fn test() { use std::time::Instant; @@ -75,28 +171,3 @@ lazy_static! { println!("It took {} seconds, roughly {}ms per entry.", now.elapsed().as_secs_f64(), now.elapsed().as_millis() as f64 / 1_000_000.0); }*/ - -pub fn fetch_channel(id: &str) -> Channel { - { - let mut cache = CACHE.lock().unwrap(); - let existing = cache.get(&id.to_string()); - - if let Some(channel) = existing { - return (*channel).clone(); - } - } - - let col = get_collection("channels"); - let result = col.find_one(doc! { "_id": id }, None).unwrap(); - - if let Some(doc) = result { - let channel: Channel = from_bson(bson::Bson::Document(doc)).expect("Failed to unwrap channel."); - - let mut cache = CACHE.lock().unwrap(); - cache.put(id.to_string(), channel.clone()); - - return channel; - } - - panic!("Channel does not exist!"); -} diff --git a/src/database/guild.rs b/src/database/guild.rs index bbce3b1..8042ed4 100644 --- a/src/database/guild.rs +++ b/src/database/guild.rs @@ -1,20 +1,26 @@ -use bson::doc; +use super::get_collection; + use serde::{Deserialize, Serialize}; +use rocket::request::FromParam; +use std::sync::{Arc, Mutex}; +use bson::{doc, from_bson}; +use rocket::http::RawStr; +use lru::LruCache; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct MemberRef { pub guild: String, pub user: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Member { #[serde(rename = "_id")] pub id: MemberRef, pub nickname: Option<String>, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Invite { pub code: String, pub creator: String, @@ -27,7 +33,7 @@ pub struct Ban { pub reason: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Guild { #[serde(rename = "_id")] pub id: String, @@ -41,3 +47,131 @@ pub struct Guild { pub default_permissions: u32, } + +lazy_static! { + static ref CACHE: Arc<Mutex<LruCache<String, Guild>>> = Arc::new(Mutex::new(LruCache::new(4_000_000))); +} + +pub fn fetch_guild(id: &str) -> Result<Option<Guild>, String> { + { + if let Ok(mut cache) = CACHE.lock() { + let existing = cache.get(&id.to_string()); + + if let Some(guild) = existing { + return Ok(Some((*guild).clone())); + } + } else { + return Err("Failed to lock cache.".to_string()); + } + } + + let col = get_collection("guilds"); + if let Ok(result) = col.find_one(doc! { "_id": id }, None) { + if let Some(doc) = result { + if let Ok(guild) = from_bson(bson::Bson::Document(doc)) as Result<Guild, _> { + let mut cache = CACHE.lock().unwrap(); + cache.put(id.to_string(), guild.clone()); + + Ok(Some(guild)) + } else { + Err("Failed to deserialize guild!".to_string()) + } + } else { + Ok(None) + } + } else { + Err("Failed to fetch guild 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) + } + } +} + +pub fn get_member(guild_id: &String, member: &String) -> Option<Member> { + if let Ok(result) = get_collection("members").find_one( + doc! { + "_id.guild": &guild_id, + "_id.user": &member, + }, + None, + ) { + if let Some(doc) = result { + Some(from_bson(bson::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) = get_collection("guilds").find_one( + doc, + mongodb::options::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(bson::Bson::Document(invite.clone())).unwrap(), + )) + } else { + None + } + } else { + None + } +} diff --git a/src/database/message.rs b/src/database/message.rs index cdf679c..08b6e19 100644 --- a/src/database/message.rs +++ b/src/database/message.rs @@ -1,12 +1,15 @@ -use super::get_collection; -use crate::notifications; use crate::notifications::events::message::Create; use crate::notifications::events::Notification; -use crate::database::channel::Channel; use crate::routes::channel::ChannelType; +use crate::database::channel::Channel; +use super::get_collection; +use crate::notifications; use bson::{doc, to_bson, UtcDateTime}; use serde::{Deserialize, Serialize}; +use rocket::request::FromParam; +use bson::from_bson; +use rocket::http::RawStr; #[derive(Serialize, Deserialize, Debug)] pub struct PreviousEntry { @@ -87,3 +90,20 @@ impl Message { } } } + +impl<'r> FromParam<'r> for Message { + type Error = &'r RawStr; + + fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { + let col = get_collection("messages"); + let result = col + .find_one(doc! { "_id": param.to_string() }, None) + .unwrap(); + + if let Some(message) = result { + Ok(from_bson(bson::Bson::Document(message)).expect("Failed to unwrap message.")) + } else { + Err(param) + } + } +} diff --git a/src/database/permissions.rs b/src/database/permissions.rs index 0064a7d..52dce26 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -1,9 +1,8 @@ use super::mutual::has_mutual_connection; use crate::database::channel::Channel; -use crate::database::guild::Member; +use crate::database::guild::{Guild, Member, get_member, fetch_guild}; use crate::database::user::UserRelationship; use crate::guards::auth::UserRef; -use crate::guards::guild::{get_member, GuildRef}; use num_enum::TryFromPrimitive; @@ -89,7 +88,7 @@ pub fn get_relationship(a: &UserRef, b: &UserRef) -> Relationship { pub struct PermissionCalculator { pub user: UserRef, pub channel: Option<Channel>, - pub guild: Option<GuildRef>, + pub guild: Option<Guild>, pub member: Option<Member>, } @@ -110,7 +109,7 @@ impl PermissionCalculator { } } - pub fn guild(self, guild: GuildRef) -> PermissionCalculator { + pub fn guild(self, guild: Guild) -> PermissionCalculator { PermissionCalculator { guild: Some(guild), ..self @@ -125,7 +124,11 @@ impl PermissionCalculator { 0..=1 => None, 2 => { if let Some(id) = &channel.guild { - GuildRef::from(id.clone()) + if let Ok(result) = fetch_guild(id) { + result + } else { + None + } } else { None } diff --git a/src/guards/channel.rs b/src/guards/channel.rs deleted file mode 100644 index 37982f2..0000000 --- a/src/guards/channel.rs +++ /dev/null @@ -1,118 +0,0 @@ -use bson::{doc, from_bson, Document}; -use mongodb::options::FindOneOptions; -use rocket::http::RawStr; -use rocket::request::FromParam; -use serde::{Deserialize, Serialize}; - -use crate::database; - -use database::channel::{ Channel, fetch_channel }; -use database::message::Message; - -impl<'r> FromParam<'r> for Channel { - type Error = &'r RawStr; - - fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { - Ok(fetch_channel(param)) - /*if let Some(channel) = fetch_channel(param) { - Ok(channel) - } else { - Err(param) - }*/ - } -} - -/* -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ChannelRef { - #[serde(rename = "_id")] - pub id: String, - #[serde(rename = "type")] - pub channel_type: u8, - - pub name: Option<String>, - pub last_message: Option<LastMessage>, - - // information required for permission calculations - pub recipients: Option<Vec<String>>, - pub guild: Option<String>, - pub owner: Option<String>, -} - -impl ChannelRef { - pub fn from(id: String) -> Option<ChannelRef> { - let channel = database::channel::fetch_channel(&id); - Some(ChannelRef { - id: channel.id, - channel_type: channel.channel_type, - - name: channel.name, - last_message: channel.last_message, - - recipients: channel.recipients, - guild: channel.guild, - owner: channel.owner - }) - - /*match database::get_collection("channels").find_one( - doc! { "_id": id }, - FindOneOptions::builder() - .projection(doc! { - "_id": 1, - "type": 1, - "name": 1, - "last_message": 1, - "recipients": 1, - "guild": 1, - "owner": 1, - }) - .build(), - ) { - Ok(result) => match result { - Some(doc) => { - Some(from_bson(bson::Bson::Document(doc)).expect("Failed to unwrap channel.")) - } - None => None, - }, - Err(_) => None, - }*/ - } - - pub fn fetch_data(&self, projection: Document) -> Option<Document> { - database::get_collection("channels") - .find_one( - doc! { "_id": &self.id }, - FindOneOptions::builder().projection(projection).build(), - ) - .expect("Failed to fetch channel from database.") - } -} - -impl<'r> FromParam<'r> for ChannelRef { - type Error = &'r RawStr; - - fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { - if let Some(channel) = ChannelRef::from(param.to_string()) { - Ok(channel) - } else { - Err(param) - } - } -}*/ - -impl<'r> FromParam<'r> for Message { - type Error = &'r RawStr; - - fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { - let col = database::get_collection("messages"); - let result = col - .find_one(doc! { "_id": param.to_string() }, None) - .unwrap(); - - if let Some(message) = result { - Ok(from_bson(bson::Bson::Document(message)).expect("Failed to unwrap message.")) - } else { - Err(param) - } - } -} diff --git a/src/guards/mod.rs b/src/guards/mod.rs index 3924edf..0e4a05d 100644 --- a/src/guards/mod.rs +++ b/src/guards/mod.rs @@ -1,3 +1 @@ pub mod auth; -pub mod channel; -pub mod guild; diff --git a/src/routes/guild.rs b/src/routes/guild.rs index 405a3d4..004ddf1 100644 --- a/src/routes/guild.rs +++ b/src/routes/guild.rs @@ -1,8 +1,8 @@ use super::channel::ChannelType; use super::Response; use crate::database::{self, channel::Channel, channel::fetch_channel, Permission, PermissionCalculator}; +use crate::database::guild::{get_invite, get_member, Guild}; use crate::guards::auth::UserRef; -use crate::guards::guild::{get_invite, get_member, GuildRef}; use crate::notifications::{ self, events::{guilds::*, Notification}, @@ -91,7 +91,7 @@ pub fn my_guilds(user: UserRef) -> Response { /// fetch a guild #[get("/<target>")] -pub fn guild(user: UserRef, target: GuildRef) -> Option<Response> { +pub fn guild(user: UserRef, target: Guild) -> Option<Response> { with_permissions!(user, target); let col = database::get_collection("channels"); @@ -134,7 +134,7 @@ pub fn guild(user: UserRef, target: GuildRef) -> Option<Response> { /// delete or leave a guild #[delete("/<target>")] -pub fn remove_guild(user: UserRef, target: GuildRef) -> Option<Response> { +pub fn remove_guild(user: UserRef, target: Guild) -> Option<Response> { with_permissions!(user, target); if user.id == target.owner { @@ -174,27 +174,41 @@ pub fn remove_guild(user: UserRef, target: GuildRef) -> Option<Response> { ) .is_ok() { - if database::get_collection("guilds") - .delete_one( + if database::get_collection("members") + .delete_many( doc! { - "_id": &target.id + "_id.guild": &target.id, }, None, ) .is_ok() { - notifications::send_message_threaded( - None, - target.id.clone(), - Notification::guild_delete(Delete { - id: target.id.clone(), - }), - ); - - Some(Response::Result(super::Status::Ok)) + if database::get_collection("guilds") + .delete_one( + doc! { + "_id": &target.id + }, + None, + ) + .is_ok() + { + notifications::send_message_threaded( + None, + target.id.clone(), + Notification::guild_delete(Delete { + id: target.id.clone(), + }), + ); + + Some(Response::Result(super::Status::Ok)) + } else { + Some(Response::InternalServerError( + json!({ "error": "Failed to delete guild." }), + )) + } } else { Some(Response::InternalServerError( - json!({ "error": "Failed to delete guild." }), + json!({ "error": "Failed to delete guild members." }), )) } } else { @@ -253,7 +267,7 @@ pub struct CreateChannel { #[post("/<target>/channels", data = "<info>")] pub fn create_channel( user: UserRef, - target: GuildRef, + target: Guild, info: Json<CreateChannel>, ) -> Option<Response> { let (permissions, _) = with_permissions!(user, target); @@ -329,7 +343,7 @@ pub struct InviteOptions { #[post("/<target>/channels/<channel>/invite", data = "<_options>")] pub fn create_invite( user: UserRef, - target: GuildRef, + target: Guild, channel: Channel, _options: Json<InviteOptions>, ) -> Option<Response> { @@ -366,7 +380,7 @@ pub fn create_invite( /// remove an invite #[delete("/<target>/invites/<code>")] -pub fn remove_invite(user: UserRef, target: GuildRef, code: String) -> Option<Response> { +pub fn remove_invite(user: UserRef, target: Guild, code: String) -> Option<Response> { let (permissions, _) = with_permissions!(user, target); if let Some((guild_id, _, invite)) = get_invite(&code, None) { @@ -407,43 +421,39 @@ pub fn remove_invite(user: UserRef, target: GuildRef, code: String) -> Option<Re /// fetch all guild invites #[get("/<target>/invites")] -pub fn fetch_invites(user: UserRef, target: GuildRef) -> Option<Response> { +pub fn fetch_invites(user: UserRef, target: Guild) -> Option<Response> { let (permissions, _) = with_permissions!(user, target); if !permissions.get_manage_server() { return Some(Response::LackingPermission(Permission::ManageServer)); } - if let Some(doc) = target.fetch_data(doc! { - "invites": 1, - }) { - Some(Response::Success(json!(doc.get_array("invites").unwrap()))) - } else { - Some(Response::InternalServerError( - json!({ "error": "Failed to fetch invites." }), - )) - } + Some(Response::Success(json!(target.invites))) } /// view an invite before joining #[get("/join/<code>", rank = 1)] pub fn fetch_invite(user: UserRef, code: String) -> Response { if let Some((guild_id, name, invite)) = get_invite(&code, user.id) { - //if let Some(channel) = ChannelRef::from(invite.channel) { - let channel = fetch_channel(&invite.channel); - Response::Success(json!({ - "guild": { - "id": guild_id, - "name": name, - }, - "channel": { - "id": channel.id, - "name": channel.name, + match fetch_channel(&invite.channel) { + Ok(result) => { + if let Some(channel) = result { + Response::Success(json!({ + "guild": { + "id": guild_id, + "name": name, + }, + "channel": { + "id": channel.id, + "name": channel.name, + } + })) + } else { + Response::NotFound(json!({ "error": "Channel does not exist." })) } - })) - /*} else { - Response::BadRequest(json!({ "error": "Failed to fetch channel." })) - }*/ + }, + Err(err) => Response::InternalServerError(json!({ "error": err })) + } } else { Response::NotFound(json!({ "error": "Failed to fetch invite or code is invalid." })) } @@ -605,7 +615,7 @@ pub fn create_guild(user: UserRef, info: Json<CreateGuild>) -> Response { /// fetch a guild's member #[get("/<target>/members")] -pub fn fetch_members(user: UserRef, target: GuildRef) -> Option<Response> { +pub fn fetch_members(user: UserRef, target: Guild) -> Option<Response> { with_permissions!(user, target); if let Ok(result) = @@ -632,7 +642,7 @@ pub fn fetch_members(user: UserRef, target: GuildRef) -> Option<Response> { /// fetch a guild member #[get("/<target>/members/<other>")] -pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> 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) { @@ -649,7 +659,7 @@ pub fn fetch_member(user: UserRef, target: GuildRef, other: String) -> Option<Re /// kick a guild member #[delete("/<target>/members/<other>")] -pub fn kick_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> { +pub fn kick_member(user: UserRef, target: Guild, other: String) -> Option<Response> { let (permissions, _) = with_permissions!(user, target); if user.id == other { @@ -705,7 +715,7 @@ pub struct BanOptions { #[put("/<target>/members/<other>/ban?<options..>")] pub fn ban_member( user: UserRef, - target: GuildRef, + target: Guild, other: String, options: Form<BanOptions>, ) -> Option<Response> { @@ -784,7 +794,7 @@ pub fn ban_member( /// unban a guild member #[delete("/<target>/members/<other>/ban")] -pub fn unban_member(user: UserRef, target: GuildRef, other: String) -> Option<Response> { +pub fn unban_member(user: UserRef, target: Guild, other: String) -> Option<Response> { let (permissions, _) = with_permissions!(user, target); if user.id == other { @@ -797,19 +807,7 @@ pub fn unban_member(user: UserRef, target: GuildRef, other: String) -> Option<Re return Some(Response::LackingPermission(Permission::BanMembers)); } - if target - .fetch_data_given( - doc! { - "bans": { - "$elemMatch": { - "id": &other - } - } - }, - doc! {}, - ) - .is_none() - { + if target.bans.iter().any(|v| v.id == other) { return Some(Response::BadRequest(json!({ "error": "User not banned." }))); } -- GitLab