mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(themes): Setup themes table (#6533)
This commit is contained in:
@ -3,6 +3,8 @@ pub mod keymanager;
|
||||
|
||||
/// Enum for Authentication Level
|
||||
pub mod authentication;
|
||||
/// Enum for Theme Lineage
|
||||
pub mod theme;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
||||
39
crates/common_utils/src/types/theme.rs
Normal file
39
crates/common_utils/src/types/theme.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::id_type;
|
||||
|
||||
/// Enum for having all the required lineage for every level.
|
||||
/// Currently being used for theme related APIs and queries.
|
||||
#[derive(Debug)]
|
||||
pub enum ThemeLineage {
|
||||
/// Tenant lineage variant
|
||||
Tenant {
|
||||
/// tenant_id: String
|
||||
tenant_id: String,
|
||||
},
|
||||
/// Org lineage variant
|
||||
Organization {
|
||||
/// tenant_id: String
|
||||
tenant_id: String,
|
||||
/// org_id: OrganizationId
|
||||
org_id: id_type::OrganizationId,
|
||||
},
|
||||
/// Merchant lineage variant
|
||||
Merchant {
|
||||
/// tenant_id: String
|
||||
tenant_id: String,
|
||||
/// org_id: OrganizationId
|
||||
org_id: id_type::OrganizationId,
|
||||
/// merchant_id: MerchantId
|
||||
merchant_id: id_type::MerchantId,
|
||||
},
|
||||
/// Profile lineage variant
|
||||
Profile {
|
||||
/// tenant_id: String
|
||||
tenant_id: String,
|
||||
/// org_id: OrganizationId
|
||||
org_id: id_type::OrganizationId,
|
||||
/// merchant_id: MerchantId
|
||||
merchant_id: id_type::MerchantId,
|
||||
/// profile_id: ProfileId
|
||||
profile_id: id_type::ProfileId,
|
||||
},
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
use common_utils::pii;
|
||||
use diesel::{associations::HasTable, ExpressionMethods};
|
||||
|
||||
pub mod sample_data;
|
||||
pub mod theme;
|
||||
|
||||
use crate::{
|
||||
query::generics, schema::users::dsl as users_dsl, user::*, PgPooledConn, StorageResult,
|
||||
|
||||
95
crates/diesel_models/src/query/user/theme.rs
Normal file
95
crates/diesel_models/src/query/user/theme.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use common_utils::types::theme::ThemeLineage;
|
||||
use diesel::{
|
||||
associations::HasTable,
|
||||
pg::Pg,
|
||||
sql_types::{Bool, Nullable},
|
||||
BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
query::generics,
|
||||
schema::themes::dsl,
|
||||
user::theme::{Theme, ThemeNew},
|
||||
PgPooledConn, StorageResult,
|
||||
};
|
||||
|
||||
impl ThemeNew {
|
||||
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Theme> {
|
||||
generics::generic_insert(conn, self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
fn lineage_filter(
|
||||
lineage: ThemeLineage,
|
||||
) -> Box<
|
||||
dyn diesel::BoxableExpression<<Self as HasTable>::Table, Pg, SqlType = Nullable<Bool>>
|
||||
+ 'static,
|
||||
> {
|
||||
match lineage {
|
||||
ThemeLineage::Tenant { tenant_id } => Box::new(
|
||||
dsl::tenant_id
|
||||
.eq(tenant_id)
|
||||
.and(dsl::org_id.is_null())
|
||||
.and(dsl::merchant_id.is_null())
|
||||
.and(dsl::profile_id.is_null())
|
||||
.nullable(),
|
||||
),
|
||||
ThemeLineage::Organization { tenant_id, org_id } => Box::new(
|
||||
dsl::tenant_id
|
||||
.eq(tenant_id)
|
||||
.and(dsl::org_id.eq(org_id))
|
||||
.and(dsl::merchant_id.is_null())
|
||||
.and(dsl::profile_id.is_null()),
|
||||
),
|
||||
ThemeLineage::Merchant {
|
||||
tenant_id,
|
||||
org_id,
|
||||
merchant_id,
|
||||
} => Box::new(
|
||||
dsl::tenant_id
|
||||
.eq(tenant_id)
|
||||
.and(dsl::org_id.eq(org_id))
|
||||
.and(dsl::merchant_id.eq(merchant_id))
|
||||
.and(dsl::profile_id.is_null()),
|
||||
),
|
||||
ThemeLineage::Profile {
|
||||
tenant_id,
|
||||
org_id,
|
||||
merchant_id,
|
||||
profile_id,
|
||||
} => Box::new(
|
||||
dsl::tenant_id
|
||||
.eq(tenant_id)
|
||||
.and(dsl::org_id.eq(org_id))
|
||||
.and(dsl::merchant_id.eq(merchant_id))
|
||||
.and(dsl::profile_id.eq(profile_id)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_lineage(
|
||||
conn: &PgPooledConn,
|
||||
lineage: ThemeLineage,
|
||||
) -> StorageResult<Self> {
|
||||
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
Self::lineage_filter(lineage),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_by_theme_id_and_lineage(
|
||||
conn: &PgPooledConn,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> StorageResult<Self> {
|
||||
generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
dsl::theme_id
|
||||
.eq(theme_id)
|
||||
.and(Self::lineage_filter(lineage)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -1262,6 +1262,26 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
themes (theme_id) {
|
||||
#[max_length = 64]
|
||||
theme_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
tenant_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
org_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
profile_id -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
last_modified_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
@ -1408,6 +1428,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
reverse_lookup,
|
||||
roles,
|
||||
routing_algorithm,
|
||||
themes,
|
||||
unified_translations,
|
||||
user_authentication_methods,
|
||||
user_key_store,
|
||||
|
||||
@ -1208,6 +1208,26 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
themes (theme_id) {
|
||||
#[max_length = 64]
|
||||
theme_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
tenant_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
org_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
profile_id -> Nullable<Varchar>,
|
||||
created_at -> Timestamp,
|
||||
last_modified_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
@ -1355,6 +1375,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
reverse_lookup,
|
||||
roles,
|
||||
routing_algorithm,
|
||||
themes,
|
||||
unified_translations,
|
||||
user_authentication_methods,
|
||||
user_key_store,
|
||||
|
||||
@ -6,8 +6,9 @@ use time::PrimitiveDateTime;
|
||||
use crate::{diesel_impl::OptionalDieselArray, enums::TotpStatus, schema::users};
|
||||
|
||||
pub mod dashboard_metadata;
|
||||
|
||||
pub mod sample_data;
|
||||
pub mod theme;
|
||||
|
||||
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
|
||||
#[diesel(table_name = users, primary_key(user_id), check_for_backend(diesel::pg::Pg))]
|
||||
pub struct User {
|
||||
|
||||
29
crates/diesel_models/src/user/theme.rs
Normal file
29
crates/diesel_models/src/user/theme.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use common_utils::id_type;
|
||||
use diesel::{Identifiable, Insertable, Queryable, Selectable};
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::schema::themes;
|
||||
|
||||
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
|
||||
#[diesel(table_name = themes, primary_key(theme_id), check_for_backend(diesel::pg::Pg))]
|
||||
pub struct Theme {
|
||||
pub theme_id: String,
|
||||
pub tenant_id: String,
|
||||
pub org_id: Option<id_type::OrganizationId>,
|
||||
pub merchant_id: Option<id_type::MerchantId>,
|
||||
pub profile_id: Option<id_type::ProfileId>,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
|
||||
#[diesel(table_name = themes)]
|
||||
pub struct ThemeNew {
|
||||
pub theme_id: String,
|
||||
pub tenant_id: String,
|
||||
pub org_id: Option<id_type::OrganizationId>,
|
||||
pub merchant_id: Option<id_type::MerchantId>,
|
||||
pub profile_id: Option<id_type::ProfileId>,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
@ -145,6 +145,7 @@ pub trait GlobalStorageInterface:
|
||||
+ user::UserInterface
|
||||
+ user_role::UserRoleInterface
|
||||
+ user_key_store::UserKeyStoreInterface
|
||||
+ user::theme::ThemeInterface
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_enums::enums::MerchantStorageScheme;
|
||||
use common_utils::{errors::CustomResult, id_type, pii, types::keymanager::KeyManagerState};
|
||||
use common_utils::{
|
||||
errors::CustomResult,
|
||||
id_type, pii,
|
||||
types::{keymanager::KeyManagerState, theme::ThemeLineage},
|
||||
};
|
||||
use diesel_models::{
|
||||
enums,
|
||||
enums::ProcessTrackerStatus,
|
||||
@ -34,7 +38,7 @@ use time::PrimitiveDateTime;
|
||||
use super::{
|
||||
dashboard_metadata::DashboardMetadataInterface,
|
||||
role::RoleInterface,
|
||||
user::{sample_data::BatchSampleDataInterface, UserInterface},
|
||||
user::{sample_data::BatchSampleDataInterface, theme::ThemeInterface, UserInterface},
|
||||
user_authentication_method::UserAuthenticationMethodInterface,
|
||||
user_key_store::UserKeyStoreInterface,
|
||||
user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload, UserRoleInterface},
|
||||
@ -3683,3 +3687,30 @@ impl UserAuthenticationMethodInterface for KafkaStore {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ThemeInterface for KafkaStore {
|
||||
async fn insert_theme(
|
||||
&self,
|
||||
theme: storage::theme::ThemeNew,
|
||||
) -> CustomResult<storage::theme::Theme, errors::StorageError> {
|
||||
self.diesel_store.insert_theme(theme).await
|
||||
}
|
||||
|
||||
async fn find_theme_by_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::theme::Theme, errors::StorageError> {
|
||||
self.diesel_store.find_theme_by_lineage(lineage).await
|
||||
}
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
&self,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::theme::Theme, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.delete_theme_by_lineage_and_theme_id(theme_id, lineage)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ use crate::{
|
||||
services::Store,
|
||||
};
|
||||
pub mod sample_data;
|
||||
pub mod theme;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait UserInterface {
|
||||
|
||||
203
crates/router/src/db/user/theme.rs
Normal file
203
crates/router/src/db/user/theme.rs
Normal file
@ -0,0 +1,203 @@
|
||||
use common_utils::types::theme::ThemeLineage;
|
||||
use diesel_models::user::theme as storage;
|
||||
use error_stack::report;
|
||||
|
||||
use super::MockDb;
|
||||
use crate::{
|
||||
connection,
|
||||
core::errors::{self, CustomResult},
|
||||
services::Store,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait ThemeInterface {
|
||||
async fn insert_theme(
|
||||
&self,
|
||||
theme: storage::ThemeNew,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError>;
|
||||
|
||||
async fn find_theme_by_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError>;
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
&self,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ThemeInterface for Store {
|
||||
async fn insert_theme(
|
||||
&self,
|
||||
theme: storage::ThemeNew,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
theme
|
||||
.insert(&conn)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
async fn find_theme_by_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
storage::Theme::find_by_lineage(&conn, lineage)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
&self,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::Theme::delete_by_theme_id_and_lineage(&conn, theme_id, lineage)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
}
|
||||
|
||||
fn check_theme_with_lineage(theme: &storage::Theme, lineage: &ThemeLineage) -> bool {
|
||||
match lineage {
|
||||
ThemeLineage::Tenant { tenant_id } => {
|
||||
&theme.tenant_id == tenant_id
|
||||
&& theme.org_id.is_none()
|
||||
&& theme.merchant_id.is_none()
|
||||
&& theme.profile_id.is_none()
|
||||
}
|
||||
ThemeLineage::Organization { tenant_id, org_id } => {
|
||||
&theme.tenant_id == tenant_id
|
||||
&& theme
|
||||
.org_id
|
||||
.as_ref()
|
||||
.is_some_and(|org_id_inner| org_id_inner == org_id)
|
||||
&& theme.merchant_id.is_none()
|
||||
&& theme.profile_id.is_none()
|
||||
}
|
||||
ThemeLineage::Merchant {
|
||||
tenant_id,
|
||||
org_id,
|
||||
merchant_id,
|
||||
} => {
|
||||
&theme.tenant_id == tenant_id
|
||||
&& theme
|
||||
.org_id
|
||||
.as_ref()
|
||||
.is_some_and(|org_id_inner| org_id_inner == org_id)
|
||||
&& theme
|
||||
.merchant_id
|
||||
.as_ref()
|
||||
.is_some_and(|merchant_id_inner| merchant_id_inner == merchant_id)
|
||||
&& theme.profile_id.is_none()
|
||||
}
|
||||
ThemeLineage::Profile {
|
||||
tenant_id,
|
||||
org_id,
|
||||
merchant_id,
|
||||
profile_id,
|
||||
} => {
|
||||
&theme.tenant_id == tenant_id
|
||||
&& theme
|
||||
.org_id
|
||||
.as_ref()
|
||||
.is_some_and(|org_id_inner| org_id_inner == org_id)
|
||||
&& theme
|
||||
.merchant_id
|
||||
.as_ref()
|
||||
.is_some_and(|merchant_id_inner| merchant_id_inner == merchant_id)
|
||||
&& theme
|
||||
.profile_id
|
||||
.as_ref()
|
||||
.is_some_and(|profile_id_inner| profile_id_inner == profile_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ThemeInterface for MockDb {
|
||||
async fn insert_theme(
|
||||
&self,
|
||||
new_theme: storage::ThemeNew,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError> {
|
||||
let mut themes = self.themes.lock().await;
|
||||
for theme in themes.iter() {
|
||||
if new_theme.theme_id == theme.theme_id {
|
||||
return Err(errors::StorageError::DuplicateValue {
|
||||
entity: "theme_id",
|
||||
key: None,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
if new_theme.tenant_id == theme.tenant_id
|
||||
&& new_theme.org_id == theme.org_id
|
||||
&& new_theme.merchant_id == theme.merchant_id
|
||||
&& new_theme.profile_id == theme.profile_id
|
||||
{
|
||||
return Err(errors::StorageError::DuplicateValue {
|
||||
entity: "lineage",
|
||||
key: None,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
let theme = storage::Theme {
|
||||
theme_id: new_theme.theme_id,
|
||||
tenant_id: new_theme.tenant_id,
|
||||
org_id: new_theme.org_id,
|
||||
merchant_id: new_theme.merchant_id,
|
||||
profile_id: new_theme.profile_id,
|
||||
created_at: new_theme.created_at,
|
||||
last_modified_at: new_theme.last_modified_at,
|
||||
};
|
||||
themes.push(theme.clone());
|
||||
|
||||
Ok(theme)
|
||||
}
|
||||
|
||||
async fn find_theme_by_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError> {
|
||||
let themes = self.themes.lock().await;
|
||||
themes
|
||||
.iter()
|
||||
.find(|theme| check_theme_with_lineage(theme, &lineage))
|
||||
.cloned()
|
||||
.ok_or(
|
||||
errors::StorageError::ValueNotFound(format!(
|
||||
"Theme with lineage {:?} not found",
|
||||
lineage
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
&self,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError> {
|
||||
let mut themes = self.themes.lock().await;
|
||||
let index = themes
|
||||
.iter()
|
||||
.position(|theme| {
|
||||
theme.theme_id == theme_id && check_theme_with_lineage(theme, &lineage)
|
||||
})
|
||||
.ok_or(errors::StorageError::ValueNotFound(format!(
|
||||
"Theme with id {} and lineage {:?} not found",
|
||||
theme_id, lineage
|
||||
)))?;
|
||||
|
||||
let theme = themes.remove(index);
|
||||
|
||||
Ok(theme)
|
||||
}
|
||||
}
|
||||
@ -60,6 +60,7 @@ pub struct MockDb {
|
||||
pub user_key_store: Arc<Mutex<Vec<store::user_key_store::UserKeyStore>>>,
|
||||
pub user_authentication_methods:
|
||||
Arc<Mutex<Vec<store::user_authentication_method::UserAuthenticationMethod>>>,
|
||||
pub themes: Arc<Mutex<Vec<store::user::theme::Theme>>>,
|
||||
}
|
||||
|
||||
impl MockDb {
|
||||
@ -105,6 +106,7 @@ impl MockDb {
|
||||
roles: Default::default(),
|
||||
user_key_store: Default::default(),
|
||||
user_authentication_methods: Default::default(),
|
||||
themes: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
3
migrations/2024-11-06-121933_setup-themes-table/down.sql
Normal file
3
migrations/2024-11-06-121933_setup-themes-table/down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX IF EXISTS themes_index;
|
||||
DROP TABLE IF EXISTS themes;
|
||||
17
migrations/2024-11-06-121933_setup-themes-table/up.sql
Normal file
17
migrations/2024-11-06-121933_setup-themes-table/up.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE IF NOT EXISTS themes (
|
||||
theme_id VARCHAR(64) PRIMARY KEY,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
org_id VARCHAR(64),
|
||||
merchant_id VARCHAR(64),
|
||||
profile_id VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
last_modified_at TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS themes_index ON themes (
|
||||
tenant_id,
|
||||
COALESCE(org_id, '0'),
|
||||
COALESCE(merchant_id, '0'),
|
||||
COALESCE(profile_id, '0')
|
||||
);
|
||||
Reference in New Issue
Block a user