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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
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 @@ -1545,6 +1545,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 @@ -1475,6 +1475,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
85 changes: 72 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 @@ -3169,7 +3172,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 @@ -3182,9 +3185,29 @@ 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;
tokio::spawn({
Copy link
Preview

Copilot AI May 2, 2025

Choose a reason for hiding this comment

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

Consider adding a retry mechanism for the asynchronous update of lineage_context in the background task to improve resilience against transient DB failures.

Copilot uses AI. Check for mistakes.

let state = state.clone();
let lineage_context = lineage_context.clone();
let user_id = user_from_token.user_id.clone();
async move {
if let Err(e) = state
.global_store
.update_user_by_user_id(
&user_id,
diesel_models::user::UserUpdate::LineageContextUpdate { lineage_context },
)
.await
{
logger::error!(
"Failed to update lineage context for user {}: {:?}",
user_id,
e
);
} else {
logger::debug!("Successfully updated lineage context for user {}", user_id);
}
}
});

let token = utils::user::generate_jwt_auth_token_with_attributes(
&state,
Expand Down Expand Up @@ -3381,7 +3404,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 @@ -3394,9 +3417,27 @@ 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;
tokio::spawn({
Copy link
Preview

Copilot AI May 2, 2025

Choose a reason for hiding this comment

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

Evaluate whether a consistent retry or fallback strategy is needed across all lineage_context update tasks (e.g. in switch_merchant and switch_profile) to ensure reliable DB updates.

Copilot uses AI. Check for mistakes.

let state = state.clone();
let lineage_context = lineage_context.clone();
let user_id = user_from_token.user_id.clone();
async move {
let _ = state
.global_store
.update_user_by_user_id(
&user_id,
diesel_models::user::UserUpdate::LineageContextUpdate { lineage_context },
)
.await
.map_err(|e| {
logger::error!(
"Failed to update lineage context for user {}: {:?}",
user_id,
e
);
});
}
});

let token = utils::user::generate_jwt_auth_token_with_attributes(
&state,
Expand Down Expand Up @@ -3514,7 +3555,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 @@ -3527,9 +3568,27 @@ 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;
tokio::spawn({
let state = state.clone();
let lineage_context = lineage_context.clone();
let user_id = user_from_token.user_id.clone();
async move {
let _ = state
.global_store
.update_user_by_user_id(
&user_id,
diesel_models::user::UserUpdate::LineageContextUpdate { lineage_context },
)
.await
.map_err(|e| {
logger::error!(
"Failed to update lineage context for user {}: {:?}",
user_id,
e
);
});
}
});

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
Loading
Loading