mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 20:23:43 +08:00
feat(themes): Create user APIs for managing themes (#8387)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -4,7 +4,8 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||
use crate::user::sample_data::SampleDataRequest;
|
||||
#[cfg(feature = "control_center_theme")]
|
||||
use crate::user::theme::{
|
||||
CreateThemeRequest, GetThemeResponse, UpdateThemeRequest, UploadFileRequest,
|
||||
CreateThemeRequest, CreateUserThemeRequest, GetThemeResponse, UpdateThemeRequest,
|
||||
UploadFileRequest,
|
||||
};
|
||||
use crate::user::{
|
||||
dashboard_metadata::{
|
||||
@ -83,6 +84,7 @@ common_utils::impl_api_event_type!(
|
||||
GetThemeResponse,
|
||||
UploadFileRequest,
|
||||
CreateThemeRequest,
|
||||
CreateUserThemeRequest,
|
||||
UpdateThemeRequest
|
||||
)
|
||||
);
|
||||
|
||||
@ -41,6 +41,14 @@ pub struct CreateThemeRequest {
|
||||
pub email_config: Option<EmailThemeConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct CreateUserThemeRequest {
|
||||
pub entity_type: EntityType,
|
||||
pub theme_name: String,
|
||||
pub theme_data: ThemeData,
|
||||
pub email_config: Option<EmailThemeConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UpdateThemeRequest {
|
||||
pub theme_data: Option<ThemeData>,
|
||||
@ -137,3 +145,9 @@ struct Urls {
|
||||
favicon_url: Option<String>,
|
||||
logo_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct EntityTypeQueryParam {
|
||||
pub entity_type: EntityType,
|
||||
}
|
||||
|
||||
@ -7534,6 +7534,8 @@ pub enum PermissionGroup {
|
||||
ReconOpsView,
|
||||
ReconOpsManage,
|
||||
InternalManage,
|
||||
ThemeView,
|
||||
ThemeManage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash, strum::EnumIter)]
|
||||
@ -7547,6 +7549,7 @@ pub enum ParentGroup {
|
||||
ReconReports,
|
||||
Account,
|
||||
Internal,
|
||||
Theme,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize)]
|
||||
@ -7577,6 +7580,7 @@ pub enum Resource {
|
||||
ReconConfig,
|
||||
RevenueRecovery,
|
||||
InternalConnector,
|
||||
Theme,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)]
|
||||
|
||||
@ -9,7 +9,6 @@ crate::impl_id_type_methods!(ProfileId, "profile_id");
|
||||
// This is to display the `ProfileId` as ProfileId(abcd)
|
||||
crate::impl_debug_id_type!(ProfileId);
|
||||
crate::impl_try_from_cow_str_id_type!(ProfileId, "profile_id");
|
||||
|
||||
crate::impl_generate_id_id_type!(ProfileId, "pro");
|
||||
crate::impl_serializable_secret_id_type!(ProfileId);
|
||||
crate::impl_queryable_id_type!(ProfileId);
|
||||
|
||||
@ -8,7 +8,7 @@ use crate::{
|
||||
|
||||
/// Enum for having all the required lineage for every level.
|
||||
/// Currently being used for theme related APIs and queries.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(tag = "entity_type", rename_all = "snake_case")]
|
||||
pub enum ThemeLineage {
|
||||
/// Tenant lineage variant
|
||||
|
||||
@ -77,6 +77,48 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
|
||||
/// Matches all themes that belong to the specified hierarchy level or below
|
||||
fn lineage_hierarchy_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).nullable()),
|
||||
ThemeLineage::Organization { tenant_id, org_id } => Box::new(
|
||||
dsl::tenant_id
|
||||
.eq(tenant_id)
|
||||
.and(dsl::org_id.eq(org_id))
|
||||
.nullable(),
|
||||
),
|
||||
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))
|
||||
.nullable(),
|
||||
),
|
||||
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))
|
||||
.nullable(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_theme_id(conn: &PgPooledConn, theme_id: String) -> StorageResult<Self> {
|
||||
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
@ -161,4 +203,35 @@ impl Theme {
|
||||
)
|
||||
.await
|
||||
}
|
||||
pub async fn delete_by_theme_id(conn: &PgPooledConn, theme_id: String) -> StorageResult<Self> {
|
||||
generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
dsl::theme_id.eq(theme_id),
|
||||
)
|
||||
.await
|
||||
}
|
||||
/// Finds all themes that match the specified lineage hierarchy.
|
||||
pub async fn find_all_by_lineage_hierarchy(
|
||||
conn: &PgPooledConn,
|
||||
lineage: ThemeLineage,
|
||||
) -> StorageResult<Vec<Self>> {
|
||||
let filter = Self::lineage_hierarchy_filter(lineage);
|
||||
|
||||
let query = <Self as HasTable>::table().filter(filter).into_boxed();
|
||||
|
||||
logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());
|
||||
|
||||
match track_database_call::<Self, _, _>(
|
||||
query.get_results_async(conn),
|
||||
DatabaseOperation::Filter,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(themes) => Ok(themes),
|
||||
Err(err) => match err {
|
||||
DieselError::NotFound => Err(report!(err)).change_context(DatabaseError::NotFound),
|
||||
_ => Err(report!(err)).change_context(DatabaseError::Others),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use api_models::user::theme as theme_api;
|
||||
use common_enums::EntityType;
|
||||
use common_utils::{
|
||||
ext_traits::{ByteSliceExt, Encode},
|
||||
types::user::ThemeLineage,
|
||||
@ -13,9 +14,11 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
core::errors::{StorageErrorExt, UserErrors, UserResponse},
|
||||
routes::SessionState,
|
||||
services::authentication::UserFromToken,
|
||||
utils::user::theme as theme_utils,
|
||||
};
|
||||
|
||||
// TODO: To be deprecated
|
||||
pub async fn get_theme_using_lineage(
|
||||
state: SessionState,
|
||||
lineage: ThemeLineage,
|
||||
@ -50,6 +53,7 @@ pub async fn get_theme_using_lineage(
|
||||
}))
|
||||
}
|
||||
|
||||
// TODO: To be deprecated
|
||||
pub async fn get_theme_using_theme_id(
|
||||
state: SessionState,
|
||||
theme_id: String,
|
||||
@ -84,6 +88,7 @@ pub async fn get_theme_using_theme_id(
|
||||
}))
|
||||
}
|
||||
|
||||
// TODO: To be deprecated
|
||||
pub async fn upload_file_to_theme_storage(
|
||||
state: SessionState,
|
||||
theme_id: String,
|
||||
@ -105,6 +110,7 @@ pub async fn upload_file_to_theme_storage(
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
// TODO: To be deprecated
|
||||
pub async fn create_theme(
|
||||
state: SessionState,
|
||||
request: theme_api::CreateThemeRequest,
|
||||
@ -166,6 +172,7 @@ pub async fn create_theme(
|
||||
}))
|
||||
}
|
||||
|
||||
// TODO: To be deprecated
|
||||
pub async fn update_theme(
|
||||
state: SessionState,
|
||||
theme_id: String,
|
||||
@ -223,14 +230,11 @@ pub async fn update_theme(
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn delete_theme(
|
||||
state: SessionState,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> UserResponse<()> {
|
||||
// TODO: To be deprecated
|
||||
pub async fn delete_theme(state: SessionState, theme_id: String) -> UserResponse<()> {
|
||||
state
|
||||
.store
|
||||
.delete_theme_by_lineage_and_theme_id(theme_id.clone(), lineage)
|
||||
.delete_theme_by_theme_id(theme_id.clone())
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
|
||||
@ -240,3 +244,331 @@ pub async fn delete_theme(
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn create_user_theme(
|
||||
state: SessionState,
|
||||
user_from_token: UserFromToken,
|
||||
request: theme_api::CreateUserThemeRequest,
|
||||
) -> UserResponse<theme_api::GetThemeResponse> {
|
||||
let email_config = if cfg!(feature = "email") {
|
||||
request.email_config.ok_or(UserErrors::MissingEmailConfig)?
|
||||
} else {
|
||||
request
|
||||
.email_config
|
||||
.unwrap_or(state.conf.theme.email_config.clone())
|
||||
};
|
||||
let lineage = theme_utils::get_theme_lineage_from_user_token(
|
||||
&user_from_token,
|
||||
&state,
|
||||
&request.entity_type,
|
||||
)
|
||||
.await?;
|
||||
let new_theme = ThemeNew::new(
|
||||
Uuid::new_v4().to_string(),
|
||||
request.theme_name,
|
||||
lineage,
|
||||
email_config,
|
||||
);
|
||||
|
||||
let db_theme = state
|
||||
.store
|
||||
.insert_theme(new_theme)
|
||||
.await
|
||||
.to_duplicate_response(UserErrors::ThemeAlreadyExists)?;
|
||||
|
||||
theme_utils::upload_file_to_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&db_theme.theme_id),
|
||||
request
|
||||
.theme_data
|
||||
.encode_to_vec()
|
||||
.change_context(UserErrors::InternalServerError)?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let file = theme_utils::retrieve_file_from_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&db_theme.theme_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let parsed_data = file
|
||||
.to_bytes()
|
||||
.parse_struct("ThemeData")
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
|
||||
email_config: db_theme.email_config(),
|
||||
theme_id: db_theme.theme_id,
|
||||
entity_type: db_theme.entity_type,
|
||||
tenant_id: db_theme.tenant_id,
|
||||
org_id: db_theme.org_id,
|
||||
merchant_id: db_theme.merchant_id,
|
||||
profile_id: db_theme.profile_id,
|
||||
theme_name: db_theme.theme_name,
|
||||
theme_data: parsed_data,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn delete_user_theme(
|
||||
state: SessionState,
|
||||
user_from_token: UserFromToken,
|
||||
theme_id: String,
|
||||
) -> UserResponse<()> {
|
||||
let db_theme = state
|
||||
.store
|
||||
.find_theme_by_theme_id(theme_id.clone())
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
|
||||
let user_role_info = user_from_token
|
||||
.get_role_info_from_db(&state)
|
||||
.await
|
||||
.attach_printable("Invalid role_id in JWT")?;
|
||||
let user_entity_type = user_role_info.get_entity_type();
|
||||
|
||||
theme_utils::can_user_access_theme(&user_from_token, &user_entity_type, &db_theme).await?;
|
||||
state
|
||||
.store
|
||||
.delete_theme_by_theme_id(theme_id.clone())
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
// TODO (#6717): Delete theme folder from the theme storage.
|
||||
// Currently there is no simple or easy way to delete a whole folder from S3.
|
||||
// So, we are not deleting the theme folder from the theme storage.
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn update_user_theme(
|
||||
state: SessionState,
|
||||
theme_id: String,
|
||||
user_from_token: UserFromToken,
|
||||
request: theme_api::UpdateThemeRequest,
|
||||
) -> UserResponse<theme_api::GetThemeResponse> {
|
||||
let db_theme = state
|
||||
.store
|
||||
.find_theme_by_theme_id(theme_id.clone())
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
|
||||
let user_role_info = user_from_token
|
||||
.get_role_info_from_db(&state)
|
||||
.await
|
||||
.attach_printable("Invalid role_id in JWT")?;
|
||||
let user_entity_type = user_role_info.get_entity_type();
|
||||
|
||||
theme_utils::can_user_access_theme(&user_from_token, &user_entity_type, &db_theme).await?;
|
||||
|
||||
let db_theme = match request.email_config {
|
||||
Some(email_config) => {
|
||||
let theme_update = ThemeUpdate::EmailConfig { email_config };
|
||||
state
|
||||
.store
|
||||
.update_theme_by_theme_id(theme_id.clone(), theme_update)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
}
|
||||
None => db_theme,
|
||||
};
|
||||
|
||||
if let Some(theme_data) = request.theme_data {
|
||||
theme_utils::upload_file_to_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&db_theme.theme_id),
|
||||
theme_data
|
||||
.encode_to_vec()
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to parse ThemeData")?,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let file = theme_utils::retrieve_file_from_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&db_theme.theme_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let parsed_data = file
|
||||
.to_bytes()
|
||||
.parse_struct("ThemeData")
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
|
||||
email_config: db_theme.email_config(),
|
||||
theme_id: db_theme.theme_id,
|
||||
entity_type: db_theme.entity_type,
|
||||
tenant_id: db_theme.tenant_id,
|
||||
org_id: db_theme.org_id,
|
||||
merchant_id: db_theme.merchant_id,
|
||||
profile_id: db_theme.profile_id,
|
||||
theme_name: db_theme.theme_name,
|
||||
theme_data: parsed_data,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn upload_file_to_user_theme_storage(
|
||||
state: SessionState,
|
||||
theme_id: String,
|
||||
user_from_token: UserFromToken,
|
||||
request: theme_api::UploadFileRequest,
|
||||
) -> UserResponse<()> {
|
||||
let db_theme = state
|
||||
.store
|
||||
.find_theme_by_theme_id(theme_id)
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
|
||||
let user_role_info = user_from_token
|
||||
.get_role_info_from_db(&state)
|
||||
.await
|
||||
.attach_printable("Invalid role_id in JWT")?;
|
||||
let user_entity_type = user_role_info.get_entity_type();
|
||||
|
||||
theme_utils::can_user_access_theme(&user_from_token, &user_entity_type, &db_theme).await?;
|
||||
theme_utils::upload_file_to_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_specific_file_key(&db_theme.theme_id, &request.asset_name),
|
||||
request.asset_data.expose(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn list_all_themes_in_lineage(
|
||||
state: SessionState,
|
||||
user: UserFromToken,
|
||||
entity_type: EntityType,
|
||||
) -> UserResponse<Vec<theme_api::GetThemeResponse>> {
|
||||
let lineage =
|
||||
theme_utils::get_theme_lineage_from_user_token(&user, &state, &entity_type).await?;
|
||||
|
||||
let db_themes = state
|
||||
.store
|
||||
.list_themes_at_and_under_lineage(lineage)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
let mut themes = Vec::new();
|
||||
for theme in db_themes {
|
||||
match theme_utils::retrieve_file_from_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&theme.theme_id),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(file) => {
|
||||
match file
|
||||
.to_bytes()
|
||||
.parse_struct("ThemeData")
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
{
|
||||
Ok(parsed_data) => {
|
||||
themes.push(theme_api::GetThemeResponse {
|
||||
email_config: theme.email_config(),
|
||||
theme_id: theme.theme_id,
|
||||
theme_name: theme.theme_name,
|
||||
entity_type: theme.entity_type,
|
||||
tenant_id: theme.tenant_id,
|
||||
org_id: theme.org_id,
|
||||
merchant_id: theme.merchant_id,
|
||||
profile_id: theme.profile_id,
|
||||
theme_data: parsed_data,
|
||||
});
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(UserErrors::ErrorRetrievingFile.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(UserErrors::ErrorRetrievingFile.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ApplicationResponse::Json(themes))
|
||||
}
|
||||
|
||||
pub async fn get_user_theme_using_theme_id(
|
||||
state: SessionState,
|
||||
user_from_token: UserFromToken,
|
||||
theme_id: String,
|
||||
) -> UserResponse<theme_api::GetThemeResponse> {
|
||||
let db_theme = state
|
||||
.store
|
||||
.find_theme_by_theme_id(theme_id.clone())
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
|
||||
let user_role_info = user_from_token
|
||||
.get_role_info_from_db(&state)
|
||||
.await
|
||||
.attach_printable("Invalid role_id in JWT")?;
|
||||
|
||||
let user_role_entity = user_role_info.get_entity_type();
|
||||
|
||||
theme_utils::can_user_access_theme(&user_from_token, &user_role_entity, &db_theme).await?;
|
||||
let file = theme_utils::retrieve_file_from_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&theme_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let parsed_data = file
|
||||
.to_bytes()
|
||||
.parse_struct("ThemeData")
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
|
||||
email_config: db_theme.email_config(),
|
||||
theme_id: db_theme.theme_id,
|
||||
theme_name: db_theme.theme_name,
|
||||
entity_type: db_theme.entity_type,
|
||||
tenant_id: db_theme.tenant_id,
|
||||
org_id: db_theme.org_id,
|
||||
merchant_id: db_theme.merchant_id,
|
||||
profile_id: db_theme.profile_id,
|
||||
theme_data: parsed_data,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_user_theme_using_lineage(
|
||||
state: SessionState,
|
||||
user_from_token: UserFromToken,
|
||||
entity_type: EntityType,
|
||||
) -> UserResponse<theme_api::GetThemeResponse> {
|
||||
let lineage =
|
||||
theme_utils::get_theme_lineage_from_user_token(&user_from_token, &state, &entity_type)
|
||||
.await?;
|
||||
let theme = state
|
||||
.store
|
||||
.find_theme_by_lineage(lineage)
|
||||
.await
|
||||
.to_not_found_response(UserErrors::ThemeNotFound)?;
|
||||
|
||||
let file = theme_utils::retrieve_file_from_theme_bucket(
|
||||
&state,
|
||||
&theme_utils::get_theme_file_key(&theme.theme_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let parsed_data = file
|
||||
.to_bytes()
|
||||
.parse_struct("ThemeData")
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
|
||||
email_config: theme.email_config(),
|
||||
theme_id: theme.theme_id,
|
||||
theme_name: theme.theme_name,
|
||||
entity_type: theme.entity_type,
|
||||
tenant_id: theme.tenant_id,
|
||||
org_id: theme.org_id,
|
||||
merchant_id: theme.merchant_id,
|
||||
profile_id: theme.profile_id,
|
||||
theme_data: parsed_data,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -4152,13 +4152,19 @@ impl ThemeInterface for KafkaStore {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
async fn delete_theme_by_theme_id(
|
||||
&self,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::theme::Theme, errors::StorageError> {
|
||||
self.diesel_store.delete_theme_by_theme_id(theme_id).await
|
||||
}
|
||||
|
||||
async fn list_themes_at_and_under_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<Vec<storage::theme::Theme>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.delete_theme_by_lineage_and_theme_id(theme_id, lineage)
|
||||
.list_themes_at_and_under_lineage(lineage)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,11 +37,15 @@ pub trait ThemeInterface {
|
||||
theme_update: ThemeUpdate,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError>;
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
async fn delete_theme_by_theme_id(
|
||||
&self,
|
||||
theme_id: String,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<storage::Theme, errors::StorageError>;
|
||||
|
||||
async fn list_themes_at_and_under_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<Vec<storage::Theme>, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -98,13 +102,21 @@ impl ThemeInterface for Store {
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
async fn delete_theme_by_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)
|
||||
storage::Theme::delete_by_theme_id(&conn, theme_id)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
async fn list_themes_at_and_under_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<Vec<storage::Theme>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
storage::Theme::find_all_by_lineage_hierarchy(&conn, lineage)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
@ -166,6 +178,57 @@ fn check_theme_with_lineage(theme: &storage::Theme, lineage: &ThemeLineage) -> b
|
||||
}
|
||||
}
|
||||
|
||||
fn check_theme_belongs_to_lineage_hierarchy(
|
||||
theme: &storage::Theme,
|
||||
lineage: &ThemeLineage,
|
||||
) -> bool {
|
||||
match lineage {
|
||||
ThemeLineage::Tenant { tenant_id } => &theme.tenant_id == tenant_id,
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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(
|
||||
@ -297,23 +360,32 @@ impl ThemeInterface for MockDb {
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_theme_by_lineage_and_theme_id(
|
||||
async fn delete_theme_by_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)
|
||||
})
|
||||
.position(|theme| theme.theme_id == theme_id)
|
||||
.ok_or(errors::StorageError::ValueNotFound(format!(
|
||||
"Theme with id {theme_id} and lineage {lineage:?} not found",
|
||||
"Theme with id {theme_id} not found"
|
||||
)))?;
|
||||
|
||||
let theme = themes.remove(index);
|
||||
|
||||
Ok(theme)
|
||||
}
|
||||
async fn list_themes_at_and_under_lineage(
|
||||
&self,
|
||||
lineage: ThemeLineage,
|
||||
) -> CustomResult<Vec<storage::Theme>, errors::StorageError> {
|
||||
let themes = self.themes.lock().await;
|
||||
let matching_themes: Vec<storage::Theme> = themes
|
||||
.iter()
|
||||
.filter(|theme| check_theme_belongs_to_lineage_hierarchy(theme, &lineage))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
Ok(matching_themes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2663,9 +2663,10 @@ impl User {
|
||||
.route(web::delete().to(user::delete_sample_data)),
|
||||
)
|
||||
}
|
||||
|
||||
// Admin Theme
|
||||
// TODO: To be deprecated
|
||||
route = route.service(
|
||||
web::scope("/theme")
|
||||
web::scope("/admin/theme")
|
||||
.service(
|
||||
web::resource("")
|
||||
.route(web::get().to(user::theme::get_theme_using_lineage))
|
||||
@ -2679,7 +2680,26 @@ impl User {
|
||||
.route(web::delete().to(user::theme::delete_theme)),
|
||||
),
|
||||
);
|
||||
|
||||
// User Theme
|
||||
route = route.service(
|
||||
web::scope("/theme")
|
||||
.service(
|
||||
web::resource("")
|
||||
.route(web::post().to(user::theme::create_user_theme))
|
||||
.route(web::get().to(user::theme::get_user_theme_using_lineage)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/list")
|
||||
.route(web::get().to(user::theme::list_all_themes_in_lineage)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{theme_id}")
|
||||
.route(web::get().to(user::theme::get_user_theme_using_theme_id))
|
||||
.route(web::put().to(user::theme::update_user_theme))
|
||||
.route(web::post().to(user::theme::upload_file_to_user_theme_storage))
|
||||
.route(web::delete().to(user::theme::delete_user_theme)),
|
||||
),
|
||||
);
|
||||
route
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,6 +302,13 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::CreateTheme
|
||||
| Flow::UpdateTheme
|
||||
| Flow::DeleteTheme
|
||||
| Flow::CreateUserTheme
|
||||
| Flow::UpdateUserTheme
|
||||
| Flow::DeleteUserTheme
|
||||
| Flow::GetUserThemeUsingThemeId
|
||||
| Flow::UploadFileToUserThemeStorage
|
||||
| Flow::GetUserThemeUsingLineage
|
||||
| Flow::ListAllThemesInLineage
|
||||
| Flow::CloneConnector => Self::User,
|
||||
|
||||
Flow::GetDataFromHyperswitchAiFlow => Self::AiWorkflow,
|
||||
|
||||
@ -8,7 +8,7 @@ use router_env::Flow;
|
||||
use crate::{
|
||||
core::{api_locking, user::theme as theme_core},
|
||||
routes::AppState,
|
||||
services::{api, authentication as auth},
|
||||
services::{api, authentication as auth, authorization::permissions::Permission},
|
||||
};
|
||||
|
||||
pub async fn get_theme_using_lineage(
|
||||
@ -124,20 +124,190 @@ pub async fn delete_theme(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
query: web::Query<ThemeLineage>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::DeleteTheme;
|
||||
let theme_id = path.into_inner();
|
||||
let lineage = query.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
lineage,
|
||||
|state, _, lineage, _| theme_core::delete_theme(state, theme_id.clone(), lineage),
|
||||
theme_id,
|
||||
|state, _, theme_id, _| theme_core::delete_theme(state, theme_id),
|
||||
&auth::AdminApiAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
pub async fn create_user_theme(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
payload: web::Json<theme_api::CreateUserThemeRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::CreateUserTheme;
|
||||
let payload = payload.into_inner();
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
|state, user: auth::UserFromToken, payload, _| {
|
||||
theme_core::create_user_theme(state, user, payload)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeWrite,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
pub async fn get_user_theme_using_theme_id(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::GetUserThemeUsingThemeId;
|
||||
let payload = path.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
|state, user: auth::UserFromToken, payload, _| {
|
||||
theme_core::get_user_theme_using_theme_id(state, user, payload)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeRead,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_user_theme(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
payload: web::Json<theme_api::UpdateThemeRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::UpdateUserTheme;
|
||||
let theme_id = path.into_inner();
|
||||
let payload = payload.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
|state, user: auth::UserFromToken, payload, _| {
|
||||
theme_core::update_user_theme(state, theme_id.clone(), user, payload)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeWrite,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_user_theme(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::DeleteUserTheme;
|
||||
let theme_id = path.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
theme_id,
|
||||
|state, user: auth::UserFromToken, theme_id, _| {
|
||||
theme_core::delete_user_theme(state, user, theme_id)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeWrite,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn upload_file_to_user_theme_storage(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
MultipartForm(payload): MultipartForm<theme_api::UploadFileAssetData>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::UploadFileToUserThemeStorage;
|
||||
let theme_id = path.into_inner();
|
||||
let payload = theme_api::UploadFileRequest {
|
||||
asset_name: payload.asset_name.into_inner(),
|
||||
asset_data: Secret::new(payload.asset_data.data.to_vec()),
|
||||
};
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
|state, user: auth::UserFromToken, payload, _| {
|
||||
theme_core::upload_file_to_user_theme_storage(state, theme_id.clone(), user, payload)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeWrite,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_all_themes_in_lineage(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
query: web::Query<theme_api::EntityTypeQueryParam>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::ListAllThemesInLineage;
|
||||
let entity_type = query.into_inner().entity_type;
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
(),
|
||||
|state, user: auth::UserFromToken, _payload, _| {
|
||||
theme_core::list_all_themes_in_lineage(state, user, entity_type)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeRead,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_user_theme_using_lineage(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
query: web::Query<theme_api::EntityTypeQueryParam>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::GetUserThemeUsingLineage;
|
||||
let entity_type = query.into_inner().entity_type;
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
(),
|
||||
|state, user: auth::UserFromToken, _payload, _| {
|
||||
theme_core::get_user_theme_using_lineage(state, user, entity_type)
|
||||
},
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::OrganizationThemeRead,
|
||||
},
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@ -48,6 +48,8 @@ fn get_group_description(group: PermissionGroup) -> Option<&'static str> {
|
||||
PermissionGroup::ReconReportsManage => Some("Manage reconciliation reports"),
|
||||
PermissionGroup::ReconOpsView => Some("View and access all reconciliation operations including reports and analytics"),
|
||||
PermissionGroup::ReconOpsManage => Some("Manage all reconciliation operations including reports and analytics"),
|
||||
PermissionGroup::ThemeView => Some("View Themes"),
|
||||
PermissionGroup::ThemeManage => Some("Manage Themes"),
|
||||
PermissionGroup::InternalManage => None, // Internal group, no user-facing description
|
||||
}
|
||||
}
|
||||
@ -62,6 +64,7 @@ pub fn get_parent_group_description(group: ParentGroup) -> Option<&'static str>
|
||||
ParentGroup::Account => Some("Create, modify and delete Merchant Details like api keys, webhooks, etc"),
|
||||
ParentGroup::ReconOps => Some("View, manage reconciliation operations like upload and process files, run reconciliation etc"),
|
||||
ParentGroup::ReconReports => Some("View, manage reconciliation reports and analytics"),
|
||||
ParentGroup::Theme => Some("Manage and view themes for the organization"),
|
||||
ParentGroup::Internal => None, // Internal group, no user-facing description
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,8 @@ impl PermissionGroupExt for PermissionGroup {
|
||||
| Self::MerchantDetailsView
|
||||
| Self::AccountView
|
||||
| Self::ReconOpsView
|
||||
| Self::ReconReportsView => PermissionScope::Read,
|
||||
| Self::ReconReportsView
|
||||
| Self::ThemeView => PermissionScope::Read,
|
||||
|
||||
Self::OperationsManage
|
||||
| Self::ConnectorsManage
|
||||
@ -34,7 +35,8 @@ impl PermissionGroupExt for PermissionGroup {
|
||||
| Self::AccountManage
|
||||
| Self::ReconOpsManage
|
||||
| Self::ReconReportsManage
|
||||
| Self::InternalManage => PermissionScope::Write,
|
||||
| Self::InternalManage
|
||||
| Self::ThemeManage => PermissionScope::Write,
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +52,8 @@ impl PermissionGroupExt for PermissionGroup {
|
||||
| Self::MerchantDetailsManage
|
||||
| Self::AccountView
|
||||
| Self::AccountManage => ParentGroup::Account,
|
||||
|
||||
Self::ThemeView | Self::ThemeManage => ParentGroup::Theme,
|
||||
Self::ReconOpsView | Self::ReconOpsManage => ParentGroup::ReconOps,
|
||||
Self::ReconReportsView | Self::ReconReportsManage => ParentGroup::ReconReports,
|
||||
Self::InternalManage => ParentGroup::Internal,
|
||||
@ -103,6 +107,8 @@ impl PermissionGroupExt for PermissionGroup {
|
||||
Self::AccountManage => vec![Self::AccountView, Self::AccountManage],
|
||||
|
||||
Self::InternalManage => vec![Self::InternalManage],
|
||||
Self::ThemeView => vec![Self::ThemeView, Self::AccountView],
|
||||
Self::ThemeManage => vec![Self::ThemeManage, Self::AccountView],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,6 +133,7 @@ impl ParentGroupExt for ParentGroup {
|
||||
Self::ReconOps => RECON_OPS.to_vec(),
|
||||
Self::ReconReports => RECON_REPORTS.to_vec(),
|
||||
Self::Internal => INTERNAL.to_vec(),
|
||||
Self::Theme => THEME.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,3 +217,5 @@ pub static RECON_REPORTS: [Resource; 4] = [
|
||||
Resource::ReconReports,
|
||||
Resource::Account,
|
||||
];
|
||||
|
||||
pub static THEME: [Resource; 1] = [Resource::Theme];
|
||||
|
||||
@ -102,6 +102,10 @@ generate_permissions! {
|
||||
InternalConnector: {
|
||||
scopes: [Write],
|
||||
entities: [Merchant]
|
||||
},
|
||||
Theme: {
|
||||
scopes: [Read,Write],
|
||||
entities: [Organization]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -137,6 +141,7 @@ pub fn get_resource_name(resource: Resource, entity_type: EntityType) -> Option<
|
||||
(Resource::Account, EntityType::Merchant) => Some("Merchant Account"),
|
||||
(Resource::Account, EntityType::Organization) => Some("Organization Account"),
|
||||
(Resource::Account, EntityType::Tenant) => Some("Tenant Account"),
|
||||
(Resource::Theme, _) => Some("Themes"),
|
||||
(Resource::InternalConnector, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +150,8 @@ pub static PREDEFINED_ROLES: LazyLock<HashMap<&'static str, RoleInfo>> = LazyLoc
|
||||
PermissionGroup::ReconOpsManage,
|
||||
PermissionGroup::ReconReportsView,
|
||||
PermissionGroup::ReconReportsManage,
|
||||
PermissionGroup::ThemeView,
|
||||
PermissionGroup::ThemeManage,
|
||||
],
|
||||
role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
|
||||
role_name: "organization_admin".to_string(),
|
||||
|
||||
@ -224,3 +224,81 @@ pub async fn get_theme_using_optional_theme_id(
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn get_theme_lineage_from_user_token(
|
||||
user_from_token: &UserFromToken,
|
||||
state: &SessionState,
|
||||
request_entity_type: &EntityType,
|
||||
) -> UserResult<ThemeLineage> {
|
||||
let tenant_id = user_from_token
|
||||
.tenant_id
|
||||
.clone()
|
||||
.unwrap_or(state.tenant.tenant_id.clone());
|
||||
let org_id = user_from_token.org_id.clone();
|
||||
let merchant_id = user_from_token.merchant_id.clone();
|
||||
let profile_id = user_from_token.profile_id.clone();
|
||||
|
||||
Ok(ThemeLineage::new(
|
||||
*request_entity_type,
|
||||
tenant_id,
|
||||
org_id,
|
||||
merchant_id,
|
||||
profile_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn can_user_access_theme(
|
||||
user: &UserFromToken,
|
||||
user_entity_type: &EntityType,
|
||||
theme: &Theme,
|
||||
) -> UserResult<()> {
|
||||
if user_entity_type < &theme.entity_type {
|
||||
return Err(UserErrors::ThemeNotFound.into());
|
||||
}
|
||||
|
||||
match theme.entity_type {
|
||||
EntityType::Tenant => {
|
||||
if user.tenant_id.as_ref() == Some(&theme.tenant_id)
|
||||
&& theme.org_id.is_none()
|
||||
&& theme.merchant_id.is_none()
|
||||
&& theme.profile_id.is_none()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(UserErrors::ThemeNotFound.into())
|
||||
}
|
||||
}
|
||||
EntityType::Organization => {
|
||||
if user.tenant_id.as_ref() == Some(&theme.tenant_id)
|
||||
&& theme.org_id.as_ref() == Some(&user.org_id)
|
||||
&& theme.merchant_id.is_none()
|
||||
&& theme.profile_id.is_none()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(UserErrors::ThemeNotFound.into())
|
||||
}
|
||||
}
|
||||
EntityType::Merchant => {
|
||||
if user.tenant_id.as_ref() == Some(&theme.tenant_id)
|
||||
&& theme.org_id.as_ref() == Some(&user.org_id)
|
||||
&& theme.merchant_id.as_ref() == Some(&user.merchant_id)
|
||||
&& theme.profile_id.is_none()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(UserErrors::ThemeNotFound.into())
|
||||
}
|
||||
}
|
||||
EntityType::Profile => {
|
||||
if user.tenant_id.as_ref() == Some(&theme.tenant_id)
|
||||
&& theme.org_id.as_ref() == Some(&user.org_id)
|
||||
&& theme.merchant_id.as_ref() == Some(&user.merchant_id)
|
||||
&& theme.profile_id.as_ref() == Some(&user.profile_id)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(UserErrors::ThemeNotFound.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,6 +523,20 @@ pub enum Flow {
|
||||
UpdateTheme,
|
||||
/// Delete theme
|
||||
DeleteTheme,
|
||||
/// Create user theme
|
||||
CreateUserTheme,
|
||||
/// Update user theme
|
||||
UpdateUserTheme,
|
||||
/// Delete user theme
|
||||
DeleteUserTheme,
|
||||
/// Upload file to user theme storage
|
||||
UploadFileToUserThemeStorage,
|
||||
/// Get user theme using theme id
|
||||
GetUserThemeUsingThemeId,
|
||||
///List All Themes In Lineage
|
||||
ListAllThemesInLineage,
|
||||
/// Get user theme using lineage
|
||||
GetUserThemeUsingLineage,
|
||||
/// List initial webhook delivery attempts
|
||||
WebhookEventInitialDeliveryAttemptList,
|
||||
/// List delivery attempts for a webhook event
|
||||
|
||||
Reference in New Issue
Block a user