Skip to content

feat(users): store and retrieve lineage_context from DB instead of Redis #7940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/api_models/src/user/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
4 changes: 2 additions & 2 deletions crates/common_utils/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions crates/common_utils/src/types/user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// User related types
pub mod core;

/// Theme related types
pub mod theme;

pub use core::*;

pub use theme::*;
28 changes: 28 additions & 0 deletions crates/common_utils/src/types/user/core.rs
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 1 addition & 1 deletion crates/diesel_models/src/query/user/theme.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,7 @@ diesel::table! {
totp_secret -> Nullable<Bytea>,
totp_recovery_codes -> Nullable<Array<Nullable<Text>>>,
last_password_modified_at -> Nullable<Timestamp>,
lineage_context -> Nullable<Jsonb>,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/schema_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,7 @@ diesel::table! {
totp_secret -> Nullable<Bytea>,
totp_recovery_codes -> Nullable<Array<Nullable<Text>>>,
last_password_modified_at -> Nullable<Timestamp>,
lineage_context -> Nullable<Jsonb>,
}
}

Expand Down
23 changes: 22 additions & 1 deletion crates/diesel_models/src/user.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,6 +24,7 @@ pub struct User {
#[diesel(deserialize_as = OptionalDieselArray<Secret<String>>)]
pub totp_recovery_codes: Option<Vec<Secret<String>>>,
pub last_password_modified_at: Option<PrimitiveDateTime>,
pub lineage_context: Option<LineageContext>,
}

#[derive(
Expand All @@ -42,6 +43,7 @@ pub struct UserNew {
pub totp_secret: Option<Encryption>,
pub totp_recovery_codes: Option<Vec<Secret<String>>>,
pub last_password_modified_at: Option<PrimitiveDateTime>,
pub lineage_context: Option<LineageContext>,
}

#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
Expand All @@ -55,6 +57,7 @@ pub struct UserUpdateInternal {
totp_secret: Option<Encryption>,
totp_recovery_codes: Option<Vec<Secret<String>>>,
last_password_modified_at: Option<PrimitiveDateTime>,
lineage_context: Option<LineageContext>,
}

#[derive(Debug)]
Expand All @@ -72,6 +75,9 @@ pub enum UserUpdate {
PasswordUpdate {
password: Secret<String>,
},
LineageContextUpdate {
lineage_context: LineageContext,
},
}

impl From<UserUpdate> for UserUpdateInternal {
Expand All @@ -87,6 +93,7 @@ impl From<UserUpdate> for UserUpdateInternal {
totp_secret: None,
totp_recovery_codes: None,
last_password_modified_at: None,
lineage_context: None,
},
UserUpdate::AccountUpdate { name, is_verified } => Self {
name,
Expand All @@ -97,6 +104,7 @@ impl From<UserUpdate> for UserUpdateInternal {
totp_secret: None,
totp_recovery_codes: None,
last_password_modified_at: None,
lineage_context: None,
},
UserUpdate::TotpUpdate {
totp_status,
Expand All @@ -111,6 +119,7 @@ impl From<UserUpdate> for UserUpdateInternal {
totp_secret,
totp_recovery_codes,
last_password_modified_at: None,
lineage_context: None,
},
UserUpdate::PasswordUpdate { password } => Self {
name: None,
Expand All @@ -121,6 +130,18 @@ impl From<UserUpdate> 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),
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/diesel_models/src/user/theme.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/configs/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
3 changes: 0 additions & 3 deletions crates/router/src/consts/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_";
2 changes: 1 addition & 1 deletion crates/router/src/core/recon.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
35 changes: 22 additions & 13 deletions crates/router/src/core/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/core/user/theme.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
23 changes: 23 additions & 0 deletions crates/router/src/db/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -208,17 +209,20 @@ impl UserInterface for MockDb {
update_user: storage::UserUpdate,
) -> CustomResult<storage::User, errors::StorageError> {
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

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()
},
Expand All @@ -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
Expand All @@ -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()
})
Expand All @@ -256,17 +268,20 @@ impl UserInterface for MockDb {
update_user: storage::UserUpdate,
) -> CustomResult<storage::User, errors::StorageError> {
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()
},
Expand All @@ -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
Expand All @@ -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()
})
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/db/user/theme.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Loading
Loading