feat(themes): Setup themes table (#6533)

This commit is contained in:
Mani Chandra
2024-11-14 14:10:20 +05:30
committed by GitHub
parent afd7f7d209
commit 29be1d4fad
15 changed files with 471 additions and 3 deletions

View File

@ -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,

View 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,
},
}

View File

@ -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,

View 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
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View 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,
}

View File

@ -145,6 +145,7 @@ pub trait GlobalStorageInterface:
+ user::UserInterface
+ user_role::UserRoleInterface
+ user_key_store::UserKeyStoreInterface
+ user::theme::ThemeInterface
+ 'static
{
}

View File

@ -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
}
}

View File

@ -11,6 +11,7 @@ use crate::{
services::Store,
};
pub mod sample_data;
pub mod theme;
#[async_trait::async_trait]
pub trait UserInterface {

View 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)
}
}

View File

@ -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(),
})
}
}

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP INDEX IF EXISTS themes_index;
DROP TABLE IF EXISTS themes;

View 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')
);