diff --git a/crates/api_models/src/user/theme.rs b/crates/api_models/src/user/theme.rs index 29b6a5d81b..0616a5aa22 100644 --- a/crates/api_models/src/user/theme.rs +++ b/crates/api_models/src/user/theme.rs @@ -2,7 +2,7 @@ use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm}; use common_enums::EntityType; use common_utils::{ id_type, - types::theme::{EmailThemeConfig, ThemeLineage}, + types::user::{EmailThemeConfig, ThemeLineage}, }; use masking::Secret; use serde::{Deserialize, Serialize}; diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index ab61cf6cca..55dcc4e5bd 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -3,8 +3,8 @@ pub mod keymanager; /// Enum for Authentication Level pub mod authentication; -/// Enum for Theme Lineage -pub mod theme; +/// User related types +pub mod user; /// types that are wrappers around primitive types pub mod primitive_wrappers; diff --git a/crates/common_utils/src/types/user.rs b/crates/common_utils/src/types/user.rs new file mode 100644 index 0000000000..e00f07309d --- /dev/null +++ b/crates/common_utils/src/types/user.rs @@ -0,0 +1,9 @@ +/// User related types +pub mod core; + +/// Theme related types +pub mod theme; + +pub use core::*; + +pub use theme::*; diff --git a/crates/common_utils/src/types/user/core.rs b/crates/common_utils/src/types/user/core.rs new file mode 100644 index 0000000000..6bdecb1283 --- /dev/null +++ b/crates/common_utils/src/types/user/core.rs @@ -0,0 +1,28 @@ +use diesel::{deserialize::FromSqlRow, expression::AsExpression}; + +use crate::id_type; + +/// Struct for lineageContext +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, AsExpression, FromSqlRow)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct LineageContext { + /// user_id: String + pub user_id: String, + + /// merchant_id: MerchantId + pub merchant_id: id_type::MerchantId, + + /// role_id: String + pub role_id: String, + + /// org_id: OrganizationId + pub org_id: id_type::OrganizationId, + + /// profile_id: ProfileId + pub profile_id: id_type::ProfileId, + + /// tenant_id: TenantId + pub tenant_id: id_type::TenantId, +} + +crate::impl_to_sql_from_sql_json!(LineageContext); diff --git a/crates/common_utils/src/types/theme.rs b/crates/common_utils/src/types/user/theme.rs similarity index 100% rename from crates/common_utils/src/types/theme.rs rename to crates/common_utils/src/types/user/theme.rs diff --git a/crates/diesel_models/src/query/user/theme.rs b/crates/diesel_models/src/query/user/theme.rs index 945ce1a9bb..e8fcf13577 100644 --- a/crates/diesel_models/src/query/user/theme.rs +++ b/crates/diesel_models/src/query/user/theme.rs @@ -1,5 +1,5 @@ use async_bb8_diesel::AsyncRunQueryDsl; -use common_utils::types::theme::ThemeLineage; +use common_utils::types::user::ThemeLineage; use diesel::{ associations::HasTable, debug_query, diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 46a36bab8c..6f978a7afe 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -1551,6 +1551,7 @@ diesel::table! { totp_secret -> Nullable, totp_recovery_codes -> Nullable>>, last_password_modified_at -> Nullable, + lineage_context -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 5349ccf728..caef78cf79 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -1483,6 +1483,7 @@ diesel::table! { totp_secret -> Nullable, totp_recovery_codes -> Nullable>>, last_password_modified_at -> Nullable, + lineage_context -> Nullable, } } diff --git a/crates/diesel_models/src/user.rs b/crates/diesel_models/src/user.rs index cf584c09b1..a05e9768c0 100644 --- a/crates/diesel_models/src/user.rs +++ b/crates/diesel_models/src/user.rs @@ -1,4 +1,4 @@ -use common_utils::{encryption::Encryption, pii}; +use common_utils::{encryption::Encryption, pii, types::user::LineageContext}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use masking::Secret; use time::PrimitiveDateTime; @@ -24,6 +24,7 @@ pub struct User { #[diesel(deserialize_as = OptionalDieselArray>)] pub totp_recovery_codes: Option>>, pub last_password_modified_at: Option, + pub lineage_context: Option, } #[derive( @@ -42,6 +43,7 @@ pub struct UserNew { pub totp_secret: Option, pub totp_recovery_codes: Option>>, pub last_password_modified_at: Option, + pub lineage_context: Option, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] @@ -55,6 +57,7 @@ pub struct UserUpdateInternal { totp_secret: Option, totp_recovery_codes: Option>>, last_password_modified_at: Option, + lineage_context: Option, } #[derive(Debug)] @@ -72,6 +75,9 @@ pub enum UserUpdate { PasswordUpdate { password: Secret, }, + LineageContextUpdate { + lineage_context: LineageContext, + }, } impl From for UserUpdateInternal { @@ -87,6 +93,7 @@ impl From for UserUpdateInternal { totp_secret: None, totp_recovery_codes: None, last_password_modified_at: None, + lineage_context: None, }, UserUpdate::AccountUpdate { name, is_verified } => Self { name, @@ -97,6 +104,7 @@ impl From for UserUpdateInternal { totp_secret: None, totp_recovery_codes: None, last_password_modified_at: None, + lineage_context: None, }, UserUpdate::TotpUpdate { totp_status, @@ -111,6 +119,7 @@ impl From for UserUpdateInternal { totp_secret, totp_recovery_codes, last_password_modified_at: None, + lineage_context: None, }, UserUpdate::PasswordUpdate { password } => Self { name: None, @@ -121,6 +130,18 @@ impl From for UserUpdateInternal { totp_status: None, totp_secret: None, totp_recovery_codes: None, + lineage_context: None, + }, + UserUpdate::LineageContextUpdate { lineage_context } => Self { + name: None, + password: None, + is_verified: None, + last_modified_at, + last_password_modified_at: None, + totp_status: None, + totp_secret: None, + totp_recovery_codes: None, + lineage_context: Some(lineage_context), }, } } diff --git a/crates/diesel_models/src/user/theme.rs b/crates/diesel_models/src/user/theme.rs index 46cc90a45e..fad4599564 100644 --- a/crates/diesel_models/src/user/theme.rs +++ b/crates/diesel_models/src/user/theme.rs @@ -1,7 +1,7 @@ use common_enums::EntityType; use common_utils::{ date_time, id_type, - types::theme::{EmailThemeConfig, ThemeLineage}, + types::user::{EmailThemeConfig, ThemeLineage}, }; use diesel::{Identifiable, Insertable, Queryable, Selectable}; use time::PrimitiveDateTime; diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 10a72c4e82..b0fd269bf7 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -7,7 +7,7 @@ use std::{ #[cfg(feature = "olap")] use analytics::{opensearch::OpenSearchConfig, ReportConfig}; use api_models::enums; -use common_utils::{ext_traits::ConfigExt, id_type, types::theme::EmailThemeConfig}; +use common_utils::{ext_traits::ConfigExt, id_type, types::user::EmailThemeConfig}; use config::{Environment, File}; use error_stack::ResultExt; #[cfg(feature = "email")] diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 649b882085..4f681cda25 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -41,6 +41,3 @@ pub const REDIS_SSO_TTL: i64 = 5 * 60; // 5 minutes pub const DEFAULT_PROFILE_NAME: &str = "default"; pub const DEFAULT_PRODUCT_TYPE: common_enums::MerchantProductType = common_enums::MerchantProductType::Orchestration; - -pub const LINEAGE_CONTEXT_TIME_EXPIRY_IN_SECS: i64 = 60 * 60 * 24 * 7; // 7 days -pub const LINEAGE_CONTEXT_PREFIX: &str = "LINEAGE_CONTEXT_"; diff --git a/crates/router/src/core/recon.rs b/crates/router/src/core/recon.rs index 3b2aacd451..17e86dc41c 100644 --- a/crates/router/src/core/recon.rs +++ b/crates/router/src/core/recon.rs @@ -1,6 +1,6 @@ use api_models::recon as recon_api; #[cfg(feature = "email")] -use common_utils::{ext_traits::AsyncExt, types::theme::ThemeLineage}; +use common_utils::{ext_traits::AsyncExt, types::user::ThemeLineage}; use error_stack::ResultExt; #[cfg(feature = "email")] use masking::{ExposeInterface, PeekInterface, Secret}; diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index ff90ca1dfa..9802edfd34 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -8,7 +8,10 @@ use api_models::{ user::{self as user_api, InviteMultipleUserResponse, NameIdUnit}, }; use common_enums::{EntityType, UserAuthType}; -use common_utils::{type_name, types::keymanager::Identifier}; +use common_utils::{ + type_name, + types::{keymanager::Identifier, user::LineageContext}, +}; #[cfg(feature = "email")] use diesel_models::user_role::UserRoleUpdate; use diesel_models::{ @@ -3237,7 +3240,7 @@ pub async fn switch_org_for_user( } }; - let lineage_context = domain::LineageContext { + let lineage_context = LineageContext { user_id: user_from_token.user_id.clone(), merchant_id: merchant_id.clone(), role_id: role_id.clone(), @@ -3250,9 +3253,11 @@ pub async fn switch_org_for_user( .clone(), }; - lineage_context - .try_set_lineage_context_in_cache(&state, user_from_token.user_id.as_str()) - .await; + utils::user::spawn_async_lineage_context_update_to_db( + &state, + &user_from_token.user_id, + lineage_context, + ); let token = utils::user::generate_jwt_auth_token_with_attributes( &state, @@ -3449,7 +3454,7 @@ pub async fn switch_merchant_for_user_in_org( } }; - let lineage_context = domain::LineageContext { + let lineage_context = LineageContext { user_id: user_from_token.user_id.clone(), merchant_id: merchant_id.clone(), role_id: role_id.clone(), @@ -3462,9 +3467,11 @@ pub async fn switch_merchant_for_user_in_org( .clone(), }; - lineage_context - .try_set_lineage_context_in_cache(&state, user_from_token.user_id.as_str()) - .await; + utils::user::spawn_async_lineage_context_update_to_db( + &state, + &user_from_token.user_id, + lineage_context, + ); let token = utils::user::generate_jwt_auth_token_with_attributes( &state, @@ -3582,7 +3589,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant( } }; - let lineage_context = domain::LineageContext { + let lineage_context = LineageContext { user_id: user_from_token.user_id.clone(), merchant_id: user_from_token.merchant_id.clone(), role_id: role_id.clone(), @@ -3595,9 +3602,11 @@ pub async fn switch_profile_for_user_in_org_and_merchant( .clone(), }; - lineage_context - .try_set_lineage_context_in_cache(&state, user_from_token.user_id.as_str()) - .await; + utils::user::spawn_async_lineage_context_update_to_db( + &state, + &user_from_token.user_id, + lineage_context, + ); let token = utils::user::generate_jwt_auth_token_with_attributes( &state, diff --git a/crates/router/src/core/user/theme.rs b/crates/router/src/core/user/theme.rs index 2a896b7158..d5c9e65306 100644 --- a/crates/router/src/core/user/theme.rs +++ b/crates/router/src/core/user/theme.rs @@ -1,7 +1,7 @@ use api_models::user::theme as theme_api; use common_utils::{ ext_traits::{ByteSliceExt, Encode}, - types::theme::ThemeLineage, + types::user::ThemeLineage, }; use diesel_models::user::theme::ThemeNew; use error_stack::ResultExt; diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index be903ef3bc..1ef8178580 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -5,7 +5,7 @@ use common_enums::enums::MerchantStorageScheme; use common_utils::{ errors::CustomResult, id_type, - types::{keymanager::KeyManagerState, theme::ThemeLineage}, + types::{keymanager::KeyManagerState, user::ThemeLineage}, }; #[cfg(feature = "v2")] use diesel_models::ephemeral_key::{ClientSecretType, ClientSecretTypeNew}; diff --git a/crates/router/src/db/user.rs b/crates/router/src/db/user.rs index 089c9fc6eb..008c1f721d 100644 --- a/crates/router/src/db/user.rs +++ b/crates/router/src/db/user.rs @@ -163,6 +163,7 @@ impl UserInterface for MockDb { totp_secret: user_data.totp_secret, totp_recovery_codes: user_data.totp_recovery_codes, last_password_modified_at: user_data.last_password_modified_at, + lineage_context: user_data.lineage_context, }; users.push(user.clone()); Ok(user) @@ -208,17 +209,20 @@ impl UserInterface for MockDb { update_user: storage::UserUpdate, ) -> CustomResult { let mut users = self.users.lock().await; + let last_modified_at = common_utils::date_time::now(); users .iter_mut() .find(|user| user.user_id == user_id) .map(|user| { *user = match &update_user { storage::UserUpdate::VerifyUser => storage::User { + last_modified_at, is_verified: true, ..user.to_owned() }, storage::UserUpdate::AccountUpdate { name, is_verified } => storage::User { name: name.clone().map(Secret::new).unwrap_or(user.name.clone()), + last_modified_at, is_verified: is_verified.unwrap_or(user.is_verified), ..user.to_owned() }, @@ -227,6 +231,7 @@ impl UserInterface for MockDb { totp_secret, totp_recovery_codes, } => storage::User { + last_modified_at, totp_status: totp_status.unwrap_or(user.totp_status), totp_secret: totp_secret.clone().or(user.totp_secret.clone()), totp_recovery_codes: totp_recovery_codes @@ -239,6 +244,13 @@ impl UserInterface for MockDb { last_password_modified_at: Some(common_utils::date_time::now()), ..user.to_owned() }, + storage::UserUpdate::LineageContextUpdate { lineage_context } => { + storage::User { + last_modified_at, + lineage_context: Some(lineage_context.clone()), + ..user.to_owned() + } + } }; user.to_owned() }) @@ -256,17 +268,20 @@ impl UserInterface for MockDb { update_user: storage::UserUpdate, ) -> CustomResult { let mut users = self.users.lock().await; + let last_modified_at = common_utils::date_time::now(); users .iter_mut() .find(|user| user.email.eq(user_email.get_inner())) .map(|user| { *user = match &update_user { storage::UserUpdate::VerifyUser => storage::User { + last_modified_at, is_verified: true, ..user.to_owned() }, storage::UserUpdate::AccountUpdate { name, is_verified } => storage::User { name: name.clone().map(Secret::new).unwrap_or(user.name.clone()), + last_modified_at, is_verified: is_verified.unwrap_or(user.is_verified), ..user.to_owned() }, @@ -275,6 +290,7 @@ impl UserInterface for MockDb { totp_secret, totp_recovery_codes, } => storage::User { + last_modified_at, totp_status: totp_status.unwrap_or(user.totp_status), totp_secret: totp_secret.clone().or(user.totp_secret.clone()), totp_recovery_codes: totp_recovery_codes @@ -287,6 +303,13 @@ impl UserInterface for MockDb { last_password_modified_at: Some(common_utils::date_time::now()), ..user.to_owned() }, + storage::UserUpdate::LineageContextUpdate { lineage_context } => { + storage::User { + last_modified_at, + lineage_context: Some(lineage_context.clone()), + ..user.to_owned() + } + } }; user.to_owned() }) diff --git a/crates/router/src/db/user/theme.rs b/crates/router/src/db/user/theme.rs index 51c0a52b3a..4202b69d99 100644 --- a/crates/router/src/db/user/theme.rs +++ b/crates/router/src/db/user/theme.rs @@ -1,4 +1,4 @@ -use common_utils::types::theme::ThemeLineage; +use common_utils::types::user::ThemeLineage; use diesel_models::user::theme as storage; use error_stack::report; diff --git a/crates/router/src/routes/user/theme.rs b/crates/router/src/routes/user/theme.rs index 69a8e9a537..6f6926dadc 100644 --- a/crates/router/src/routes/user/theme.rs +++ b/crates/router/src/routes/user/theme.rs @@ -1,7 +1,7 @@ use actix_multipart::form::MultipartForm; use actix_web::{web, HttpRequest, HttpResponse}; use api_models::user::theme as theme_api; -use common_utils::types::theme::ThemeLineage; +use common_utils::types::user::ThemeLineage; use masking::Secret; use router_env::Flow; diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index d663f72cec..0328ea988d 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -1,6 +1,6 @@ use api_models::user::dashboard_metadata::ProdIntent; use common_enums::{EntityType, MerchantProductType}; -use common_utils::{errors::CustomResult, pii, types::theme::EmailThemeConfig}; +use common_utils::{errors::CustomResult, pii, types::user::EmailThemeConfig}; use error_stack::ResultExt; use external_services::email::{EmailContents, EmailData, EmailError}; use masking::{ExposeInterface, Secret}; diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index ca48d424d2..45eb094053 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -23,7 +23,6 @@ use hyperswitch_domain_models::api::ApplicationResponse; use masking::{ExposeInterface, PeekInterface, Secret}; use once_cell::sync::Lazy; use rand::distributions::{Alphanumeric, DistString}; -use router_env::logger; use time::PrimitiveDateTime; use unicode_segmentation::UnicodeSegmentation; #[cfg(feature = "keymanager_create")] @@ -925,6 +924,7 @@ impl TryFrom for storage_user::UserNew { last_password_modified_at: value .password .and_then(|password_inner| password_inner.is_temporary.not().then_some(now)), + lineage_context: None, }) } } @@ -1499,54 +1499,3 @@ where .change_context(UserErrors::InternalServerError) } } - -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct LineageContext { - pub user_id: String, - pub merchant_id: id_type::MerchantId, - pub role_id: String, - pub org_id: id_type::OrganizationId, - pub profile_id: id_type::ProfileId, - pub tenant_id: id_type::TenantId, -} - -impl LineageContext { - pub async fn try_get_lineage_context_from_cache( - state: &SessionState, - user_id: &str, - ) -> Option { - // The errors are not handled here because we don't want to fail the request if the cache operation fails. - // The errors are logged for debugging purposes. - match utils::user::get_lineage_context_from_cache(state, user_id).await { - Ok(Some(ctx)) => Some(ctx), - Ok(None) => { - logger::debug!("Lineage context not found in Redis for user {}", user_id); - None - } - Err(e) => { - logger::error!( - "Failed to retrieve lineage context from Redis for user {}: {:?}", - user_id, - e - ); - None - } - } - } - - pub async fn try_set_lineage_context_in_cache(&self, state: &SessionState, user_id: &str) { - // The errors are not handled here because we don't want to fail the request if the cache operation fails. - // The errors are logged for debugging purposes. - if let Err(e) = - utils::user::set_lineage_context_in_cache(state, user_id, self.clone()).await - { - logger::error!( - "Failed to set lineage context in Redis for user {}: {:?}", - user_id, - e - ); - } else { - logger::debug!("Lineage context cached for user {}", user_id); - } - } -} diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index 0214d3cdd1..7bc11383da 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -1,11 +1,12 @@ use common_enums::TokenPurpose; -use common_utils::id_type; +use common_utils::{id_type, types::user::LineageContext}; use diesel_models::{ enums::{UserRoleVersion, UserStatus}, user_role::UserRole, }; use error_stack::ResultExt; use masking::Secret; +use router_env::logger; use super::UserFromStorage; use crate::{ @@ -13,7 +14,6 @@ use crate::{ db::user_role::ListUserRolesByUserIdPayload, routes::SessionState, services::authentication as auth, - types::domain::LineageContext, utils, }; @@ -129,13 +129,25 @@ impl JWTFlow { user_role: &UserRole, ) -> UserResult> { let user_id = next_flow.user.get_user_id(); - let cached_lineage_context = - LineageContext::try_get_lineage_context_from_cache(state, user_id).await; + // Fetch lineage context from DB + let lineage_context_from_db = state + .global_store + .find_user_by_id(user_id) + .await + .inspect_err(|e| { + logger::error!( + "Failed to fetch lineage context from DB for user {}: {:?}", + user_id, + e + ) + }) + .ok() + .and_then(|user| user.lineage_context); - let new_lineage_context = match cached_lineage_context { + let new_lineage_context = match lineage_context_from_db { Some(ctx) => { let tenant_id = ctx.tenant_id.clone(); - let user_role_match_v1 = state + let user_role_match_v2 = state .global_store .find_user_role_by_user_id_and_lineage( &ctx.user_id, @@ -143,15 +155,15 @@ impl JWTFlow { &ctx.org_id, &ctx.merchant_id, &ctx.profile_id, - UserRoleVersion::V1, + UserRoleVersion::V2, ) .await .is_ok(); - if user_role_match_v1 { + if user_role_match_v2 { ctx } else { - let user_role_match_v2 = state + let user_role_match_v1 = state .global_store .find_user_role_by_user_id_and_lineage( &ctx.user_id, @@ -159,12 +171,12 @@ impl JWTFlow { &ctx.org_id, &ctx.merchant_id, &ctx.profile_id, - UserRoleVersion::V2, + UserRoleVersion::V1, ) .await .is_ok(); - if user_role_match_v2 { + if user_role_match_v1 { ctx } else { // fallback to default lineage if cached context is invalid @@ -179,9 +191,11 @@ impl JWTFlow { } }; - new_lineage_context - .try_set_lineage_context_in_cache(state, user_id) - .await; + utils::user::spawn_async_lineage_context_update_to_db( + state, + user_id, + new_lineage_context.clone(), + ); auth::AuthToken::new_token( new_lineage_context.user_id, diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 0c5fd187f9..ea3edd8dfc 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -3,19 +3,19 @@ use std::sync::Arc; use api_models::user as user_api; use common_enums::UserAuthType; use common_utils::{ - encryption::Encryption, errors::CustomResult, id_type, type_name, types::keymanager::Identifier, + encryption::Encryption, + errors::CustomResult, + id_type, type_name, + types::{keymanager::Identifier, user::LineageContext}, }; use diesel_models::organization::{self, OrganizationBridge}; use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; use redis_interface::RedisConnectionPool; -use router_env::env; +use router_env::{env, logger}; use crate::{ - consts::user::{ - LINEAGE_CONTEXT_PREFIX, LINEAGE_CONTEXT_TIME_EXPIRY_IN_SECS, REDIS_SSO_PREFIX, - REDIS_SSO_TTL, - }, + consts::user::{REDIS_SSO_PREFIX, REDIS_SSO_TTL}, core::errors::{StorageError, UserErrors, UserResult}, routes::SessionState, services::{ @@ -23,7 +23,7 @@ use crate::{ authorization::roles::RoleInfo, }, types::{ - domain::{self, LineageContext, MerchantAccount, UserFromStorage}, + domain::{self, MerchantAccount, UserFromStorage}, transformers::ForeignFrom, }, }; @@ -341,50 +341,35 @@ pub async fn validate_email_domain_auth_type_using_db( .ok_or(UserErrors::InvalidUserAuthMethodOperation.into()) } -pub async fn get_lineage_context_from_cache( - state: &SessionState, - user_id: &str, -) -> UserResult> { - let connection = get_redis_connection_for_global_tenant(state)?; - let key = format!("{}{}", LINEAGE_CONTEXT_PREFIX, user_id); - let lineage_context = connection - .get_key::>(&key.into()) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to get lineage context from redis")?; - - match lineage_context { - Some(json_str) => { - let ctx = serde_json::from_str::(&json_str) - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to deserialize LineageContext from JSON")?; - Ok(Some(ctx)) - } - None => Ok(None), - } -} - -pub async fn set_lineage_context_in_cache( +pub fn spawn_async_lineage_context_update_to_db( state: &SessionState, user_id: &str, lineage_context: LineageContext, -) -> UserResult<()> { - let connection = get_redis_connection_for_global_tenant(state)?; - let key = format!("{}{}", LINEAGE_CONTEXT_PREFIX, user_id); - let serialized_lineage_context: String = serde_json::to_string(&lineage_context) - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to serialize LineageContext")?; - connection - .set_key_with_expiry( - &key.into(), - serialized_lineage_context, - LINEAGE_CONTEXT_TIME_EXPIRY_IN_SECS, - ) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to set lineage context in redis")?; - - Ok(()) +) { + let state = state.clone(); + let lineage_context = lineage_context.clone(); + let user_id = user_id.to_owned(); + tokio::spawn(async move { + match state + .global_store + .update_user_by_user_id( + &user_id, + diesel_models::user::UserUpdate::LineageContextUpdate { lineage_context }, + ) + .await + { + Ok(_) => { + logger::debug!("Successfully updated lineage context for user {}", user_id); + } + Err(e) => { + logger::error!( + "Failed to update lineage context for user {}: {:?}", + user_id, + e + ); + } + } + }); } pub fn generate_env_specific_merchant_id(value: String) -> UserResult { diff --git a/crates/router/src/utils/user/theme.rs b/crates/router/src/utils/user/theme.rs index 0b9d4e2829..33e1450f09 100644 --- a/crates/router/src/utils/user/theme.rs +++ b/crates/router/src/utils/user/theme.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use common_enums::EntityType; -use common_utils::{ext_traits::AsyncExt, id_type, types::theme::ThemeLineage}; +use common_utils::{ext_traits::AsyncExt, id_type, types::user::ThemeLineage}; use diesel_models::user::theme::Theme; use error_stack::ResultExt; use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore; diff --git a/crates/router/src/workflows/api_key_expiry.rs b/crates/router/src/workflows/api_key_expiry.rs index 2ff527a046..4df78847b3 100644 --- a/crates/router/src/workflows/api_key_expiry.rs +++ b/crates/router/src/workflows/api_key_expiry.rs @@ -1,4 +1,4 @@ -use common_utils::{errors::ValidationError, ext_traits::ValueExt, types::theme::ThemeLineage}; +use common_utils::{errors::ValidationError, ext_traits::ValueExt, types::user::ThemeLineage}; use diesel_models::{ enums as storage_enums, process_tracker::business_status, ApiKeyExpiryTrackingData, }; diff --git a/migrations/2025-04-29-144409_add_lineage_context_to_users/down.sql b/migrations/2025-04-29-144409_add_lineage_context_to_users/down.sql new file mode 100644 index 0000000000..989e7ae64a --- /dev/null +++ b/migrations/2025-04-29-144409_add_lineage_context_to_users/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE users DROP COLUMN IF EXISTS lineage_context; diff --git a/migrations/2025-04-29-144409_add_lineage_context_to_users/up.sql b/migrations/2025-04-29-144409_add_lineage_context_to_users/up.sql new file mode 100644 index 0000000000..cbae98f97c --- /dev/null +++ b/migrations/2025-04-29-144409_add_lineage_context_to_users/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE users ADD COLUMN IF NOT EXISTS lineage_context JSONB;