Skip to content
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

feat(users): Incorporate themes in user APIs #6772

Merged
merged 22 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
22e0290
feat: add theme_id in user APIs
ThisIsMani Dec 5, 2024
71b56e1
feat: add themes in emails
ThisIsMani Dec 6, 2024
bab5fc3
Merge branch 'main' of https://github.com/juspay/hyperswitch into the…
ThisIsMani Dec 6, 2024
a699d8a
refactor: remove unnecessary key in create theme
ThisIsMani Dec 6, 2024
bed7408
Merge branch 'main' of https://github.com/juspay/hyperswitch into the…
ThisIsMani Dec 6, 2024
268d11d
feat: enable tenant level themes
ThisIsMani Dec 6, 2024
1044075
chore: run formatter
hyperswitch-bot[bot] Dec 6, 2024
1fe8ae4
fix: clippy
ThisIsMani Dec 9, 2024
7ae216b
refactor: remove `AuthIdQueryParam` api model
ThisIsMani Dec 9, 2024
6ebc5c2
refactor: replace secondary_color with background and foreground colors
ThisIsMani Dec 9, 2024
bc07122
refactor: make `email_config` in `create_theme` request optional
ThisIsMani Dec 9, 2024
f0561c1
refactor: rename `entity_logo` to `entity_logo_url`
ThisIsMani Dec 9, 2024
255b438
refactor: make `email_config` in `create_theme` request non-optional
ThisIsMani Dec 9, 2024
c11b6e2
refactor: move email theme config from email to theme
ThisIsMani Dec 9, 2024
2e87602
refactor: make `email_config` in `create_theme` request optional
ThisIsMani Dec 9, 2024
6470b34
refactor: remove unnecessary configs from `env_specific`
ThisIsMani Dec 9, 2024
4a91d99
fix: add tenant lineages in get_higher_lineages function
ThisIsMani Dec 9, 2024
23e8804
refactor: use correct entity_type in resend invite
ThisIsMani Dec 9, 2024
f64fd6c
fix: clippy
ThisIsMani Dec 9, 2024
f61ae14
refactor: set backend to aws_s3 in env_specific envs
ThisIsMani Dec 10, 2024
495ee4a
Merge branch 'main' of https://github.com/juspay/hyperswitch into the…
ThisIsMani Dec 10, 2024
1047781
refactor: use new function to get role
ThisIsMani Dec 10, 2024
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
9 changes: 8 additions & 1 deletion config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -796,5 +796,12 @@ host = "localhost" # Client Host
port = 7000 # Client Port
service = "dynamo" # Service name

[theme_storage]
[theme.storage]
file_storage_backend = "file_system" # Theme storage backend to be used

[theme.email_config]
entity_name = "Hyperswitch" # Name of the entity to be showed in emails
entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails
foreground_color = "#000000" # Foreground color of email text
primary_color = "#006DF9" # Primary color of email body
background_color = "#FFFFFF" # Background color of email body
13 changes: 10 additions & 3 deletions config/deployments/env_specific.toml
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,16 @@ host = "localhost" # Client Host
port = 7000 # Client Port
service = "dynamo" # Service name

[theme_storage]
file_storage_backend = "aws_s3" # Theme storage backend to be used
[theme.storage]
file_storage_backend = "file_system" # Theme storage backend to be used
ThisIsMani marked this conversation as resolved.
Show resolved Hide resolved

[theme_storage.aws_s3]
[theme.storage.aws_s3]
region = "bucket_region" # AWS region where the S3 bucket for theme storage is located
bucket_name = "bucket" # AWS S3 bucket name for theme storage

[theme.email_config]
entity_name = "Hyperswitch" # Name of the entity to be showed in emails
entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails
foreground_color = "#000000" # Foreground color of email text
primary_color = "#006DF9" # Primary color of email body
background_color = "#FFFFFF" # Background color of email body
11 changes: 9 additions & 2 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -795,5 +795,12 @@ host = "localhost"
port = 7000
service = "dynamo"

[theme_storage]
file_storage_backend = "file_system"
[theme.storage]
file_storage_backend = "file_system" # Theme storage backend to be used

[theme.email_config]
entity_name = "Hyperswitch" # Name of the entity to be showed in emails
entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails
foreground_color = "#000000" # Foreground color of email text
primary_color = "#006DF9" # Primary color of email body
background_color = "#FFFFFF" # Background color of email body
9 changes: 8 additions & 1 deletion config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -692,5 +692,12 @@ prod_intent_recipient_email = "[email protected]" # Recipient email for prod
email_role_arn = "" # The amazon resource name ( arn ) of the role which has permission to send emails
sts_role_session_name = "" # An identifier for the assumed role session, used to uniquely identify a session.

[theme_storage]
[theme.storage]
file_storage_backend = "file_system" # Theme storage backend to be used

[theme.email_config]
entity_name = "Hyperswitch" # Name of the entity to be showed in emails
entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails
foreground_color = "#000000" # Foreground color of email text
primary_color = "#006DF9" # Primary color of email body
background_color = "#FFFFFF" # Background color of email body
4 changes: 3 additions & 1 deletion crates/api_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ pub struct GetUserDetailsResponse {
pub recovery_codes_left: Option<usize>,
pub profile_id: id_type::ProfileId,
pub entity_type: EntityType,
pub theme_id: Option<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
Expand Down Expand Up @@ -345,8 +346,9 @@ pub struct SsoSignInRequest {
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct AuthIdQueryParam {
pub struct AuthIdAndThemeIdQueryParam {
pub auth_id: Option<String>,
pub theme_id: Option<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
Expand Down
8 changes: 7 additions & 1 deletion crates/api_models/src/user/theme.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm};
use common_enums::EntityType;
use common_utils::{id_type, types::theme::ThemeLineage};
use common_utils::{
id_type,
types::theme::{EmailThemeConfig, ThemeLineage},
};
use masking::Secret;
use serde::{Deserialize, Serialize};

Expand All @@ -13,6 +16,7 @@ pub struct GetThemeResponse {
pub org_id: Option<id_type::OrganizationId>,
pub merchant_id: Option<id_type::MerchantId>,
pub profile_id: Option<id_type::ProfileId>,
pub email_config: EmailThemeConfig,
pub theme_data: ThemeData,
}

Expand All @@ -35,12 +39,14 @@ pub struct CreateThemeRequest {
pub lineage: ThemeLineage,
pub theme_name: String,
pub theme_data: ThemeData,
pub email_config: Option<EmailThemeConfig>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateThemeRequest {
pub lineage: ThemeLineage,
pub theme_data: ThemeData,
// TODO: Add support to update email config
}

// All the below structs are for the theme.json file,
Expand Down
112 changes: 102 additions & 10 deletions crates/common_utils/src/types/theme.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use common_enums::EntityType;
use serde::{Deserialize, Serialize};

use crate::{
events::{ApiEventMetric, ApiEventsType},
Expand All @@ -7,15 +8,14 @@ use crate::{

/// Enum for having all the required lineage for every level.
/// Currently being used for theme related APIs and queries.
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "entity_type", rename_all = "snake_case")]
pub enum ThemeLineage {
// TODO: Add back Tenant variant when we introduce Tenant Variant in EntityType
// /// Tenant lineage variant
// Tenant {
// /// tenant_id: String
// tenant_id: String,
// },
/// Tenant lineage variant
Tenant {
/// tenant_id: TenantId
tenant_id: id_type::TenantId,
},
/// Org lineage variant
Organization {
/// tenant_id: TenantId
Expand Down Expand Up @@ -48,9 +48,35 @@ pub enum ThemeLineage {
impl_api_event_type!(Miscellaneous, (ThemeLineage));

impl ThemeLineage {
/// Constructor for ThemeLineage
pub fn new(
entity_type: EntityType,
tenant_id: id_type::TenantId,
org_id: id_type::OrganizationId,
merchant_id: id_type::MerchantId,
profile_id: id_type::ProfileId,
) -> Self {
match entity_type {
EntityType::Tenant => Self::Tenant { tenant_id },
EntityType::Organization => Self::Organization { tenant_id, org_id },
EntityType::Merchant => Self::Merchant {
tenant_id,
org_id,
merchant_id,
},
EntityType::Profile => Self::Profile {
tenant_id,
org_id,
merchant_id,
profile_id,
},
}
}

/// Get the entity_type from the lineage
pub fn entity_type(&self) -> EntityType {
match self {
Self::Tenant { .. } => EntityType::Tenant,
Self::Organization { .. } => EntityType::Organization,
Self::Merchant { .. } => EntityType::Merchant,
Self::Profile { .. } => EntityType::Profile,
Expand All @@ -60,7 +86,8 @@ impl ThemeLineage {
/// Get the tenant_id from the lineage
pub fn tenant_id(&self) -> &id_type::TenantId {
match self {
Self::Organization { tenant_id, .. }
Self::Tenant { tenant_id }
| Self::Organization { tenant_id, .. }
| Self::Merchant { tenant_id, .. }
| Self::Profile { tenant_id, .. } => tenant_id,
}
Expand All @@ -69,6 +96,7 @@ impl ThemeLineage {
/// Get the org_id from the lineage
pub fn org_id(&self) -> Option<&id_type::OrganizationId> {
match self {
Self::Tenant { .. } => None,
Self::Organization { org_id, .. }
| Self::Merchant { org_id, .. }
| Self::Profile { org_id, .. } => Some(org_id),
Expand All @@ -78,7 +106,7 @@ impl ThemeLineage {
/// Get the merchant_id from the lineage
pub fn merchant_id(&self) -> Option<&id_type::MerchantId> {
match self {
Self::Organization { .. } => None,
Self::Tenant { .. } | Self::Organization { .. } => None,
Self::Merchant { merchant_id, .. } | Self::Profile { merchant_id, .. } => {
Some(merchant_id)
}
Expand All @@ -88,8 +116,72 @@ impl ThemeLineage {
/// Get the profile_id from the lineage
pub fn profile_id(&self) -> Option<&id_type::ProfileId> {
match self {
Self::Organization { .. } | Self::Merchant { .. } => None,
Self::Tenant { .. } | Self::Organization { .. } | Self::Merchant { .. } => None,
Self::Profile { profile_id, .. } => Some(profile_id),
}
}

/// Get higher lineages from the current lineage
pub fn get_same_and_higher_lineages(self) -> Vec<Self> {
match &self {
Self::Tenant { .. } => vec![self],
Self::Organization { tenant_id, .. } => vec![
Self::Tenant {
tenant_id: tenant_id.clone(),
},
self,
],
Self::Merchant {
tenant_id, org_id, ..
} => vec![
Self::Tenant {
tenant_id: tenant_id.clone(),
},
Self::Organization {
tenant_id: tenant_id.clone(),
org_id: org_id.clone(),
},
self,
],
Self::Profile {
tenant_id,
org_id,
merchant_id,
..
} => vec![
Self::Tenant {
tenant_id: tenant_id.clone(),
},
Self::Organization {
tenant_id: tenant_id.clone(),
org_id: org_id.clone(),
},
Self::Merchant {
tenant_id: tenant_id.clone(),
org_id: org_id.clone(),
merchant_id: merchant_id.clone(),
},
self,
],
}
}
}

/// Struct for holding the theme settings for email
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct EmailThemeConfig {
/// The entity name to be used in the email
pub entity_name: String,

/// The URL of the entity logo to be used in the email
pub entity_logo_url: String,

/// The primary color to be used in the email
pub primary_color: String,

/// The foreground color to be used in the email
pub foreground_color: String,

/// The background color to be used in the email
pub background_color: String,
}
65 changes: 54 additions & 11 deletions crates/diesel_models/src/query/user/theme.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use async_bb8_diesel::AsyncRunQueryDsl;
use common_utils::types::theme::ThemeLineage;
use diesel::{
associations::HasTable,
debug_query,
pg::Pg,
result::Error as DieselError,
sql_types::{Bool, Nullable},
BoolExpressionMethods, ExpressionMethods,
BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, QueryDsl,
};
use error_stack::{report, ResultExt};
use router_env::logger;

use crate::{
query::generics,
errors::DatabaseError,
query::generics::{
self,
db_metrics::{track_database_call, DatabaseOperation},
},
schema::themes::dsl,
user::theme::{Theme, ThemeNew},
PgPooledConn, StorageResult,
Expand All @@ -27,15 +36,14 @@ impl Theme {
+ 'static,
> {
match lineage {
// TODO: Add back Tenant variant when we introduce Tenant Variant in EntityType
// 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::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)
Expand Down Expand Up @@ -77,6 +85,41 @@ impl Theme {
.await
}

pub async fn find_most_specific_theme_in_lineage(
conn: &PgPooledConn,
lineage: ThemeLineage,
) -> StorageResult<Self> {
let query = <Self as HasTable>::table().into_boxed();

let query =
lineage
.get_same_and_higher_lineages()
.into_iter()
.fold(query, |mut query, lineage| {
query = query.or_filter(Self::lineage_filter(lineage));
query
});

logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

let data: Vec<Self> = match track_database_call::<Self, _, _>(
query.get_results_async(conn),
DatabaseOperation::Filter,
)
.await
{
Ok(value) => Ok(value),
Err(err) => match err {
DieselError::NotFound => Err(report!(err)).change_context(DatabaseError::NotFound),
_ => Err(report!(err)).change_context(DatabaseError::Others),
},
}?;

data.into_iter()
.min_by_key(|theme| theme.entity_type)
.ok_or(report!(DatabaseError::NotFound))
}

pub async fn find_by_lineage(
conn: &PgPooledConn,
lineage: ThemeLineage,
Expand Down
9 changes: 9 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,15 @@ diesel::table! {
entity_type -> Varchar,
#[max_length = 64]
theme_name -> Varchar,
#[max_length = 64]
email_primary_color -> Varchar,
#[max_length = 64]
email_foreground_color -> Varchar,
#[max_length = 64]
email_background_color -> Varchar,
#[max_length = 64]
email_entity_name -> Varchar,
email_entity_logo_url -> Text,
}
}

Expand Down
Loading
Loading