From 09678939eccec88da8b87a23dd5d6f7c4ccbf909 Mon Sep 17 00:00:00 2001 From: OmegaJak Date: Mon, 2 Oct 2023 18:48:40 -0500 Subject: [PATCH] Add redis caching to getting a user's project ids --- src/database/models/project_item.rs | 12 ++++++-- src/database/models/user_item.rs | 46 ++++++++++++++++++++++++++--- src/routes/v2/analytics_get.rs | 2 +- src/routes/v2/project_creation.rs | 3 +- src/routes/v2/teams.rs | 4 ++- src/routes/v2/users.rs | 2 +- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index a7d85679..3aaa34ca 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -1,9 +1,10 @@ -use super::ids::*; +use super::{ids::*, User}; use crate::database::models; use crate::database::models::DatabaseError; use crate::models::ids::base62_impl::{parse_base62, to_base62}; use crate::models::projects::{MonetizationStatus, ProjectStatus}; use chrono::{DateTime, Utc}; +use futures::TryStreamExt; use redis::cmd; use serde::{Deserialize, Serialize}; @@ -404,16 +405,21 @@ impl Project { models::TeamMember::clear_cache(project.inner.team_id, redis).await?; - sqlx::query!( + let affected_user_ids = sqlx::query!( " DELETE FROM team_members WHERE team_id = $1 + RETURNING user_id ", project.inner.team_id as TeamId, ) - .execute(&mut *transaction) + .fetch_many(&mut *transaction) + .try_filter_map(|e| async { Ok(e.right().map(|x| UserId(x.user_id))) }) + .try_collect::>() .await?; + User::clear_project_cache(&affected_user_ids, redis).await?; + sqlx::query!( " DELETE FROM teams diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index a46456e8..053e07bb 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; const USERS_NAMESPACE: &str = "users"; const USER_USERNAMES_NAMESPACE: &str = "users_usernames"; -// const USERS_PROJECTS_NAMESPACE: &str = "users_projects"; +const USERS_PROJECTS_NAMESPACE: &str = "users_projects"; const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes #[derive(Deserialize, Serialize, Clone, Debug)] @@ -298,13 +298,27 @@ impl User { pub async fn get_projects<'a, E>( user_id: UserId, exec: E, - ) -> Result, sqlx::Error> + redis: &deadpool_redis::Pool, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { use futures::stream::TryStreamExt; - let projects = sqlx::query!( + let mut redis = redis.get().await?; + + let cached_projects = cmd("GET") + .arg(format!("{}:{}", USERS_PROJECTS_NAMESPACE, user_id.0)) + .query_async::<_, Option>(&mut redis) + .await? + .and_then(|x| serde_json::from_str::>(&x).ok()); + + if let Some(projects) = cached_projects { + log::info!("Returning cached!"); + return Ok(projects); + } + + let db_projects = sqlx::query!( " SELECT m.id FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE @@ -318,7 +332,15 @@ impl User { .try_collect::>() .await?; - Ok(projects) + cmd("SET") + .arg(format!("{}:{}", USERS_PROJECTS_NAMESPACE, user_id.0)) + .arg(serde_json::to_string(&db_projects)?) + .arg("EX") + .arg(DEFAULT_EXPIRY) + .query_async::<_, ()>(&mut redis) + .await?; + + Ok(db_projects) } pub async fn get_collections<'a, E>( @@ -392,6 +414,22 @@ impl User { Ok(()) } + pub async fn clear_project_cache( + user_ids: &[UserId], + redis: &deadpool_redis::Pool, + ) -> Result<(), DatabaseError> { + let mut redis = redis.get().await?; + let mut cmd = cmd("DEL"); + + for user_id in user_ids { + cmd.arg(format!("{}:{}", USERS_PROJECTS_NAMESPACE, user_id.0)); + } + + cmd.query_async(&mut redis).await?; + + Ok(()) + } + pub async fn remove( id: UserId, full: bool, diff --git a/src/routes/v2/analytics_get.rs b/src/routes/v2/analytics_get.rs index d09932a9..0a497242 100644 --- a/src/routes/v2/analytics_get.rs +++ b/src/routes/v2/analytics_get.rs @@ -488,7 +488,7 @@ async fn filter_allowed_ids( if project_ids.is_none() && version_ids.is_none() { if let Some(user) = &user_option { project_ids = Some( - user_item::User::get_projects(user.id.into(), &***pool) + user_item::User::get_projects(user.id.into(), &***pool, &redis) .await? .into_iter() .map(|x| ProjectId::from(x).to_string()) diff --git a/src/routes/v2/project_creation.rs b/src/routes/v2/project_creation.rs index a0e057d8..ed274e04 100644 --- a/src/routes/v2/project_creation.rs +++ b/src/routes/v2/project_creation.rs @@ -1,7 +1,7 @@ use super::version_creation::InitialVersionData; use crate::auth::{get_user_from_headers, AuthenticationError}; use crate::database::models::thread_item::ThreadBuilder; -use crate::database::models::{self, image_item}; +use crate::database::models::{self, image_item, User}; use crate::file_hosting::{FileHost, FileHostingError}; use crate::models::error::ApiError; use crate::models::ids::ImageId; @@ -791,6 +791,7 @@ async fn project_create_inner( let now = Utc::now(); let id = project_builder_actual.insert(&mut *transaction).await?; + User::clear_project_cache(&[current_user.id.into()], redis).await?; for image_id in project_create_data.uploaded_images { if let Some(db_image) = diff --git a/src/routes/v2/teams.rs b/src/routes/v2/teams.rs index 34985ae3..29e027fe 100644 --- a/src/routes/v2/teams.rs +++ b/src/routes/v2/teams.rs @@ -1,7 +1,7 @@ use crate::auth::{get_user_from_headers, is_authorized}; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::team_item::TeamAssociationId; -use crate::database::models::{Organization, Team, TeamMember}; +use crate::database::models::{Organization, Team, TeamMember, User}; use crate::database::Project; use crate::models::notifications::NotificationBody; use crate::models::pats::Scopes; @@ -348,6 +348,7 @@ pub async fn join_team( ) .await?; + User::clear_project_cache(&[current_user.id.into()], &redis).await?; TeamMember::clear_cache(team_id, &redis).await?; transaction.commit().await?; @@ -954,6 +955,7 @@ pub async fn remove_team_member( } TeamMember::clear_cache(id, &redis).await?; + User::clear_project_cache(&[current_user.id.into()], &redis).await?; transaction.commit().await?; Ok(HttpResponse::NoContent().body("")) diff --git a/src/routes/v2/users.rs b/src/routes/v2/users.rs index 6adfe6a8..d111cc4e 100644 --- a/src/routes/v2/users.rs +++ b/src/routes/v2/users.rs @@ -143,7 +143,7 @@ pub async fn projects_list( .map(|y| y.role.is_mod() || y.id == user_id) .unwrap_or(false); - let project_data = User::get_projects(id, &**pool).await?; + let project_data = User::get_projects(id, &**pool, &redis).await?; let response: Vec<_> = crate::database::Project::get_many_ids(&project_data, &**pool, &redis)