feat(user): add email apis and new enums for metadata (#3053)

Co-authored-by: Rachit Naithani <rachit.naithani@juspay.in>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Rachit Naithani <81706961+racnan@users.noreply.github.com>
This commit is contained in:
Apoorv Dixit
2023-12-05 13:10:17 +05:30
committed by GitHub
parent 5b62731399
commit 1c3d260dc3
19 changed files with 727 additions and 53 deletions

View File

@ -12,8 +12,12 @@ pub enum UserErrors {
InternalServerError,
#[error("InvalidCredentials")]
InvalidCredentials,
#[error("UserNotFound")]
UserNotFound,
#[error("UserExists")]
UserExists,
#[error("LinkInvalid")]
LinkInvalid,
#[error("InvalidOldPassword")]
InvalidOldPassword,
#[error("EmailParsingError")]
@ -60,12 +64,21 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
"Incorrect email or password",
None,
)),
Self::UserNotFound => AER::Unauthorized(ApiError::new(
sub_code,
2,
"Email doesnt exist. Register",
None,
)),
Self::UserExists => AER::BadRequest(ApiError::new(
sub_code,
3,
"An account already exists with this email",
None,
)),
Self::LinkInvalid => {
AER::Unauthorized(ApiError::new(sub_code, 4, "Invalid or expired link", None))
}
Self::InvalidOldPassword => AER::BadRequest(ApiError::new(
sub_code,
6,

View File

@ -11,7 +11,7 @@ use router_env::logger;
use super::errors::{UserErrors, UserResponse};
#[cfg(feature = "email")]
use crate::services::email::types as email_types;
use crate::services::email::{types as email_types, types::EmailToken};
use crate::{
consts,
db::user::UserInterface,
@ -235,8 +235,7 @@ pub async fn change_password(
user.compare_password(request.old_password)
.change_context(UserErrors::InvalidOldPassword)?;
let new_password_hash =
crate::utils::user::password::generate_password_hash(request.new_password)?;
let new_password_hash = utils::user::password::generate_password_hash(request.new_password)?;
let _ = UserInterface::update_user_by_user_id(
&*state.store,
@ -253,6 +252,182 @@ pub async fn change_password(
Ok(ApplicationResponse::StatusOk)
}
#[cfg(feature = "email")]
pub async fn forgot_password(
state: AppState,
request: user_api::ForgotPasswordRequest,
) -> UserResponse<()> {
let user_email = domain::UserEmail::from_pii_email(request.email)?;
let user_from_db = state
.store
.find_user_by_email(user_email.get_secret().expose().as_str())
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::UserNotFound)
} else {
e.change_context(UserErrors::InternalServerError)
}
})
.map(domain::UserFromStorage::from)?;
let email_contents = email_types::ResetPassword {
recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?,
settings: state.conf.clone(),
user_name: domain::UserName::new(user_from_db.get_name())?,
subject: "Get back to Hyperswitch - Reset Your Password Now",
};
state
.email_client
.compose_and_send_email(
Box::new(email_contents),
state.conf.proxy.https_url.as_ref(),
)
.await
.map_err(|e| e.change_context(UserErrors::InternalServerError))?;
Ok(ApplicationResponse::StatusOk)
}
#[cfg(feature = "email")]
pub async fn reset_password(
state: AppState,
request: user_api::ResetPasswordRequest,
) -> UserResponse<()> {
let token = auth::decode_jwt::<EmailToken>(request.token.expose().as_str(), &state)
.await
.change_context(UserErrors::LinkInvalid)?;
let password = domain::UserPassword::new(request.password)?;
let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;
//TODO: Create Update by email query
let user_id = state
.store
.find_user_by_email(token.get_email())
.await
.change_context(UserErrors::InternalServerError)?
.user_id;
state
.store
.update_user_by_user_id(
user_id.as_str(),
storage_user::UserUpdate::AccountUpdate {
name: None,
password: Some(hash_password),
is_verified: Some(true),
},
)
.await
.change_context(UserErrors::InternalServerError)?;
//TODO: Update User role status for invited user
Ok(ApplicationResponse::StatusOk)
}
#[cfg(feature = "email")]
pub async fn invite_user(
state: AppState,
request: user_api::InviteUserRequest,
user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::InviteUserResponse> {
let inviter_user = state
.store
.find_user_by_id(user_from_token.user_id.as_str())
.await
.change_context(UserErrors::InternalServerError)?;
if inviter_user.email == request.email {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("User Inviting themself");
}
utils::user_role::validate_role_id(request.role_id.as_str())?;
let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
let invitee_user = state
.store
.find_user_by_email(invitee_email.clone().get_secret().expose().as_str())
.await;
if let Ok(invitee_user) = invitee_user {
let invitee_user_from_db = domain::UserFromStorage::from(invitee_user);
let now = common_utils::date_time::now();
use diesel_models::user_role::UserRoleNew;
state
.store
.insert_user_role(UserRoleNew {
user_id: invitee_user_from_db.get_user_id().to_owned(),
merchant_id: user_from_token.merchant_id,
role_id: request.role_id,
org_id: user_from_token.org_id,
status: UserStatus::Active,
created_by: user_from_token.user_id.clone(),
last_modified_by: user_from_token.user_id,
created_at: now,
last_modified_at: now,
})
.await
.map_err(|e| {
if e.current_context().is_db_unique_violation() {
e.change_context(UserErrors::UserExists)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;
Ok(ApplicationResponse::Json(user_api::InviteUserResponse {
is_email_sent: false,
}))
} else if invitee_user
.as_ref()
.map_err(|e| e.current_context().is_db_not_found())
.err()
.unwrap_or(false)
{
let new_user = domain::NewUser::try_from((request.clone(), user_from_token))?;
new_user
.insert_user_in_db(state.store.as_ref())
.await
.change_context(UserErrors::InternalServerError)?;
new_user
.clone()
.insert_user_role_in_db(state.clone(), request.role_id, UserStatus::InvitationSent)
.await
.change_context(UserErrors::InternalServerError)?;
let email_contents = email_types::InviteUser {
recipient_email: invitee_email,
user_name: domain::UserName::new(new_user.get_name())?,
settings: state.conf.clone(),
subject: "You have been invited to join Hyperswitch Community!",
};
let send_email_result = state
.email_client
.compose_and_send_email(
Box::new(email_contents),
state.conf.proxy.https_url.as_ref(),
)
.await;
logger::info!(?send_email_result);
Ok(ApplicationResponse::Json(user_api::InviteUserResponse {
is_email_sent: send_email_result.is_ok(),
}))
} else {
Err(UserErrors::InternalServerError.into())
}
}
pub async fn create_internal_user(
state: AppState,
request: user_api::CreateInternalUserRequest,

View File

@ -81,12 +81,17 @@ fn parse_set_request(data_enum: api::SetMetaDataRequest) -> UserResult<types::Me
api::SetMetaDataRequest::IntegrationMethod(req) => {
Ok(types::MetaData::IntegrationMethod(req))
}
api::SetMetaDataRequest::ConfigurationType(req) => {
Ok(types::MetaData::ConfigurationType(req))
}
api::SetMetaDataRequest::IntegrationCompleted => {
Ok(types::MetaData::IntegrationCompleted(true))
}
api::SetMetaDataRequest::SPRoutingConfigured(req) => {
Ok(types::MetaData::SPRoutingConfigured(req))
}
api::SetMetaDataRequest::Feedback(req) => Ok(types::MetaData::Feedback(req)),
api::SetMetaDataRequest::ProdIntent(req) => Ok(types::MetaData::ProdIntent(req)),
api::SetMetaDataRequest::SPTestPayment => Ok(types::MetaData::SPTestPayment(true)),
api::SetMetaDataRequest::DownloadWoocom => Ok(types::MetaData::DownloadWoocom(true)),
api::SetMetaDataRequest::ConfigureWoocom => Ok(types::MetaData::ConfigureWoocom(true)),
@ -110,10 +115,13 @@ fn parse_get_request(data_enum: api::GetMetaDataRequest) -> DBEnum {
api::GetMetaDataRequest::ConfiguredRouting => DBEnum::ConfiguredRouting,
api::GetMetaDataRequest::TestPayment => DBEnum::TestPayment,
api::GetMetaDataRequest::IntegrationMethod => DBEnum::IntegrationMethod,
api::GetMetaDataRequest::ConfigurationType => DBEnum::ConfigurationType,
api::GetMetaDataRequest::IntegrationCompleted => DBEnum::IntegrationCompleted,
api::GetMetaDataRequest::StripeConnected => DBEnum::StripeConnected,
api::GetMetaDataRequest::PaypalConnected => DBEnum::PaypalConnected,
api::GetMetaDataRequest::SPRoutingConfigured => DBEnum::SpRoutingConfigured,
api::GetMetaDataRequest::Feedback => DBEnum::Feedback,
api::GetMetaDataRequest::ProdIntent => DBEnum::ProdIntent,
api::GetMetaDataRequest::SPTestPayment => DBEnum::SpTestPayment,
api::GetMetaDataRequest::DownloadWoocom => DBEnum::DownloadWoocom,
api::GetMetaDataRequest::ConfigureWoocom => DBEnum::ConfigureWoocom,
@ -158,6 +166,10 @@ fn into_response(
let resp = utils::deserialize_to_response(data)?;
Ok(api::GetMetaDataResponse::IntegrationMethod(resp))
}
DBEnum::ConfigurationType => {
let resp = utils::deserialize_to_response(data)?;
Ok(api::GetMetaDataResponse::ConfigurationType(resp))
}
DBEnum::IntegrationCompleted => Ok(api::GetMetaDataResponse::IntegrationCompleted(
data.is_some(),
)),
@ -173,6 +185,14 @@ fn into_response(
let resp = utils::deserialize_to_response(data)?;
Ok(api::GetMetaDataResponse::SPRoutingConfigured(resp))
}
DBEnum::Feedback => {
let resp = utils::deserialize_to_response(data)?;
Ok(api::GetMetaDataResponse::Feedback(resp))
}
DBEnum::ProdIntent => {
let resp = utils::deserialize_to_response(data)?;
Ok(api::GetMetaDataResponse::ProdIntent(resp))
}
DBEnum::SpTestPayment => Ok(api::GetMetaDataResponse::SPTestPayment(data.is_some())),
DBEnum::DownloadWoocom => Ok(api::GetMetaDataResponse::DownloadWoocom(data.is_some())),
DBEnum::ConfigureWoocom => Ok(api::GetMetaDataResponse::ConfigureWoocom(data.is_some())),
@ -282,15 +302,54 @@ async fn insert_metadata(
.await
}
types::MetaData::IntegrationMethod(data) => {
utils::insert_merchant_scoped_metadata_to_db(
let mut metadata = utils::insert_merchant_scoped_metadata_to_db(
state,
user.user_id,
user.merchant_id,
user.org_id,
user.user_id.clone(),
user.merchant_id.clone(),
user.org_id.clone(),
metadata_key,
data,
data.clone(),
)
.await
.await;
if utils::is_update_required(&metadata) {
metadata = utils::update_merchant_scoped_metadata(
state,
user.user_id,
user.merchant_id,
user.org_id,
metadata_key,
data,
)
.await
.change_context(UserErrors::InternalServerError);
}
metadata
}
types::MetaData::ConfigurationType(data) => {
let mut metadata = utils::insert_merchant_scoped_metadata_to_db(
state,
user.user_id.clone(),
user.merchant_id.clone(),
user.org_id.clone(),
metadata_key,
data.clone(),
)
.await;
if utils::is_update_required(&metadata) {
metadata = utils::update_merchant_scoped_metadata(
state,
user.user_id,
user.merchant_id,
user.org_id,
metadata_key,
data,
)
.await
.change_context(UserErrors::InternalServerError);
}
metadata
}
types::MetaData::IntegrationCompleted(data) => {
utils::insert_merchant_scoped_metadata_to_db(
@ -336,6 +395,56 @@ async fn insert_metadata(
)
.await
}
types::MetaData::Feedback(data) => {
let mut metadata = utils::insert_user_scoped_metadata_to_db(
state,
user.user_id.clone(),
user.merchant_id.clone(),
user.org_id.clone(),
metadata_key,
data.clone(),
)
.await;
if utils::is_update_required(&metadata) {
metadata = utils::update_user_scoped_metadata(
state,
user.user_id,
user.merchant_id,
user.org_id,
metadata_key,
data,
)
.await
.change_context(UserErrors::InternalServerError);
}
metadata
}
types::MetaData::ProdIntent(data) => {
let mut metadata = utils::insert_user_scoped_metadata_to_db(
state,
user.user_id.clone(),
user.merchant_id.clone(),
user.org_id.clone(),
metadata_key,
data.clone(),
)
.await;
if utils::is_update_required(&metadata) {
metadata = utils::update_user_scoped_metadata(
state,
user.user_id,
user.merchant_id,
user.org_id,
metadata_key,
data,
)
.await
.change_context(UserErrors::InternalServerError);
}
metadata
}
types::MetaData::SPTestPayment(data) => {
utils::insert_merchant_scoped_metadata_to_db(
state,
@ -400,7 +509,8 @@ async fn fetch_metadata(
metadata_keys: Vec<DBEnum>,
) -> UserResult<Vec<DashboardMetadata>> {
let mut dashboard_metadata = Vec::with_capacity(metadata_keys.len());
let (merchant_scoped_enums, _) = utils::separate_metadata_type_based_on_scope(metadata_keys);
let (merchant_scoped_enums, user_scoped_enums) =
utils::separate_metadata_type_based_on_scope(metadata_keys);
if !merchant_scoped_enums.is_empty() {
let mut res = utils::get_merchant_scoped_metadata_from_db(
@ -413,6 +523,18 @@ async fn fetch_metadata(
dashboard_metadata.append(&mut res);
}
if !user_scoped_enums.is_empty() {
let mut res = utils::get_user_scoped_metadata_from_db(
state,
user.user_id.to_owned(),
user.merchant_id.to_owned(),
user.org_id.to_owned(),
user_scoped_enums,
)
.await?;
dashboard_metadata.append(&mut res);
}
Ok(dashboard_metadata)
}

View File

@ -997,7 +997,7 @@ impl RefundInterface for MockDb {
let mut refund_meta_data = api_models::refunds::RefundListMetaData {
connector: vec![],
currency: vec![],
status: vec![],
refund_status: vec![],
};
let mut unique_connectors = HashSet::new();
@ -1016,7 +1016,7 @@ impl RefundInterface for MockDb {
refund_meta_data.connector = unique_connectors.into_iter().collect();
refund_meta_data.currency = unique_currencies.into_iter().collect();
refund_meta_data.status = unique_statuses.into_iter().collect();
refund_meta_data.refund_status = unique_statuses.into_iter().collect();
Ok(refund_meta_data)
}

View File

@ -860,6 +860,9 @@ impl User {
.service(
web::resource("/connect_account").route(web::post().to(user_connect_account)),
)
.service(web::resource("/forgot_password").route(web::post().to(forgot_password)))
.service(web::resource("/reset_password").route(web::post().to(reset_password)))
.service(web::resource("user/invite").route(web::post().to(invite_user)))
.service(
web::resource("/signup_with_merchant_id")
.route(web::post().to(user_signup_with_merchant_id)),

View File

@ -163,6 +163,9 @@ impl From<Flow> for ApiIdentifier {
| Flow::DeleteSampleData
| Flow::UserMerchantAccountList
| Flow::GetUserDetails
| Flow::ForgotPassword
| Flow::ResetPassword
| Flow::InviteUser
| Flow::UserSignUpWithMerchantId => Self::User,
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {

View File

@ -294,3 +294,60 @@ pub async fn get_user_details(state: web::Data<AppState>, req: HttpRequest) -> H
))
.await
}
#[cfg(feature = "email")]
pub async fn forgot_password(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<user_api::ForgotPasswordRequest>,
) -> HttpResponse {
let flow = Flow::ForgotPassword;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
payload.into_inner(),
|state, _, payload| user_core::forgot_password(state, payload),
&auth::NoAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "email")]
pub async fn reset_password(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<user_api::ResetPasswordRequest>,
) -> HttpResponse {
let flow = Flow::ResetPassword;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
payload.into_inner(),
|state, _, payload| user_core::reset_password(state, payload),
&auth::NoAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "email")]
pub async fn invite_user(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<user_api::InviteUserRequest>,
) -> HttpResponse {
let flow = Flow::InviteUser;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
payload.into_inner(),
|state, user, payload| user_core::invite_user(state, payload, user),
&auth::JWTAuth(Permission::UsersWrite),
api_locking::LockAction::NotApplicable,
))
.await
}

View File

@ -66,6 +66,10 @@ impl EmailToken {
};
jwt::generate_jwt(&token_payload, settings).await
}
pub fn get_email(&self) -> &str {
self.email.as_str()
}
}
pub fn get_link_with_token(

View File

@ -259,6 +259,15 @@ impl From<UserMerchantCreateRequestWithToken> for NewUserOrganization {
}
}
type InviteeUserRequestWithInvitedUserToken = (user_api::InviteUserRequest, UserFromToken);
impl From<InviteeUserRequestWithInvitedUserToken> for NewUserOrganization {
fn from(_value: InviteeUserRequestWithInvitedUserToken) -> Self {
let new_organization = api_org::OrganizationNew::new(None);
let db_organization = ForeignFrom::foreign_from(new_organization);
Self(db_organization)
}
}
#[derive(Clone)]
pub struct MerchantId(String);
@ -420,6 +429,19 @@ impl TryFrom<user_api::CreateInternalUserRequest> for NewUserMerchant {
}
}
impl TryFrom<InviteeUserRequestWithInvitedUserToken> for NewUserMerchant {
type Error = error_stack::Report<UserErrors>;
fn try_from(value: InviteeUserRequestWithInvitedUserToken) -> UserResult<Self> {
let merchant_id = MerchantId::new(value.clone().1.merchant_id)?;
let new_organization = NewUserOrganization::from(value);
Ok(Self {
company_name: None,
merchant_id,
new_organization,
})
}
}
type UserMerchantCreateRequestWithToken =
(UserFromStorage, user_api::UserMerchantCreate, UserFromToken);
@ -657,6 +679,26 @@ impl TryFrom<UserMerchantCreateRequestWithToken> for NewUser {
}
}
impl TryFrom<InviteeUserRequestWithInvitedUserToken> for NewUser {
type Error = error_stack::Report<UserErrors>;
fn try_from(value: InviteeUserRequestWithInvitedUserToken) -> UserResult<Self> {
let user_id = uuid::Uuid::new_v4().to_string();
let email = value.0.email.clone().try_into()?;
let name = UserName::new(value.0.name.clone())?;
let password = password::generate_password_hash(uuid::Uuid::new_v4().to_string().into())?;
let password = UserPassword::new(password)?;
let new_merchant = NewUserMerchant::try_from(value)?;
Ok(Self {
user_id,
name,
email,
password,
new_merchant,
})
}
}
#[derive(Clone)]
pub struct UserFromStorage(pub storage_user::User);

View File

@ -13,10 +13,13 @@ pub enum MetaData {
ConfiguredRouting(api::ConfiguredRouting),
TestPayment(api::TestPayment),
IntegrationMethod(api::IntegrationMethod),
ConfigurationType(api::ConfigurationType),
IntegrationCompleted(bool),
StripeConnected(api::ProcessorConnected),
PaypalConnected(api::ProcessorConnected),
SPRoutingConfigured(api::ConfiguredRouting),
Feedback(api::Feedback),
ProdIntent(api::ProdIntent),
SPTestPayment(bool),
DownloadWoocom(bool),
ConfigureWoocom(bool),
@ -36,10 +39,13 @@ impl From<&MetaData> for DBEnum {
MetaData::ConfiguredRouting(_) => Self::ConfiguredRouting,
MetaData::TestPayment(_) => Self::TestPayment,
MetaData::IntegrationMethod(_) => Self::IntegrationMethod,
MetaData::ConfigurationType(_) => Self::ConfigurationType,
MetaData::IntegrationCompleted(_) => Self::IntegrationCompleted,
MetaData::StripeConnected(_) => Self::StripeConnected,
MetaData::PaypalConnected(_) => Self::PaypalConnected,
MetaData::SPRoutingConfigured(_) => Self::SpRoutingConfigured,
MetaData::Feedback(_) => Self::Feedback,
MetaData::ProdIntent(_) => Self::ProdIntent,
MetaData::SPTestPayment(_) => Self::SpTestPayment,
MetaData::DownloadWoocom(_) => Self::DownloadWoocom,
MetaData::ConfigureWoocom(_) => Self::ConfigureWoocom,

View File

@ -50,23 +50,40 @@ impl RefundDbExt for Refund {
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.order(dsl::modified_at.desc())
.into_boxed();
let mut search_by_pay_or_ref_id = false;
match &refund_list_details.payment_id {
Some(pid) => {
filter = filter.filter(dsl::payment_id.eq(pid.to_owned()));
}
None => {
filter = filter.limit(limit).offset(offset);
}
};
match &refund_list_details.refund_id {
Some(ref_id) => {
filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned()));
}
None => {
filter = filter.limit(limit).offset(offset);
}
if let (Some(pid), Some(ref_id)) = (
&refund_list_details.payment_id,
&refund_list_details.refund_id,
) {
search_by_pay_or_ref_id = true;
filter = filter
.filter(dsl::payment_id.eq(pid.to_owned()))
.or_filter(dsl::refund_id.eq(ref_id.to_owned()))
.limit(limit)
.offset(offset);
};
if !search_by_pay_or_ref_id {
match &refund_list_details.payment_id {
Some(pid) => {
filter = filter.filter(dsl::payment_id.eq(pid.to_owned()));
}
None => {
filter = filter.limit(limit).offset(offset);
}
};
}
if !search_by_pay_or_ref_id {
match &refund_list_details.refund_id {
Some(ref_id) => {
filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned()));
}
None => {
filter = filter.limit(limit).offset(offset);
}
};
}
match &refund_list_details.profile_id {
Some(profile_id) => {
filter = filter
@ -163,7 +180,7 @@ impl RefundDbExt for Refund {
let meta = api_models::refunds::RefundListMetaData {
connector: filter_connector,
currency: filter_currency,
status: filter_status,
refund_status: filter_status,
};
Ok(meta)
@ -179,12 +196,28 @@ impl RefundDbExt for Refund {
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.into_boxed();
if let Some(pay_id) = &refund_list_details.payment_id {
filter = filter.filter(dsl::payment_id.eq(pay_id.to_owned()));
let mut search_by_pay_or_ref_id = false;
if let (Some(pid), Some(ref_id)) = (
&refund_list_details.payment_id,
&refund_list_details.refund_id,
) {
search_by_pay_or_ref_id = true;
filter = filter
.filter(dsl::payment_id.eq(pid.to_owned()))
.or_filter(dsl::refund_id.eq(ref_id.to_owned()));
};
if !search_by_pay_or_ref_id {
if let Some(pay_id) = &refund_list_details.payment_id {
filter = filter.filter(dsl::payment_id.eq(pay_id.to_owned()));
}
}
if let Some(ref_id) = &refund_list_details.refund_id {
filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned()));
if !search_by_pay_or_ref_id {
if let Some(ref_id) = &refund_list_details.refund_id {
filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned()));
}
}
if let Some(profile_id) = &refund_list_details.profile_id {
filter = filter.filter(dsl::profile_id.eq(profile_id.to_owned()));

View File

@ -6,7 +6,7 @@ use api_models::user::dashboard_metadata::{
};
use diesel_models::{
enums::DashboardMetadata as DBEnum,
user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew},
user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew, DashboardMetadataUpdate},
};
use error_stack::{IntoReport, ResultExt};
use masking::Secret;
@ -50,6 +50,40 @@ pub async fn insert_merchant_scoped_metadata_to_db(
e.change_context(UserErrors::InternalServerError)
})
}
pub async fn insert_user_scoped_metadata_to_db(
state: &AppState,
user_id: String,
merchant_id: String,
org_id: String,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {
let now = common_utils::date_time::now();
let data_value = serde_json::to_value(metadata_value)
.into_report()
.change_context(UserErrors::InternalServerError)
.attach_printable("Error Converting Struct To Serde Value")?;
state
.store
.insert_metadata(DashboardMetadataNew {
user_id: Some(user_id.clone()),
merchant_id,
org_id,
data_key: metadata_key,
data_value,
created_by: user_id.clone(),
created_at: now,
last_modified_by: user_id,
last_modified_at: now,
})
.await
.map_err(|e| {
if e.current_context().is_db_unique_violation() {
return e.change_context(UserErrors::MetadataAlreadySet);
}
e.change_context(UserErrors::InternalServerError)
})
}
pub async fn get_merchant_scoped_metadata_from_db(
state: &AppState,
@ -73,6 +107,88 @@ pub async fn get_merchant_scoped_metadata_from_db(
}
}
}
pub async fn get_user_scoped_metadata_from_db(
state: &AppState,
user_id: String,
merchant_id: String,
org_id: String,
metadata_keys: Vec<DBEnum>,
) -> UserResult<Vec<DashboardMetadata>> {
match state
.store
.find_user_scoped_dashboard_metadata(&user_id, &merchant_id, &org_id, metadata_keys)
.await
{
Ok(data) => Ok(data),
Err(e) => {
if e.current_context().is_db_not_found() {
return Ok(Vec::with_capacity(0));
}
Err(e
.change_context(UserErrors::InternalServerError)
.attach_printable("DB Error Fetching DashboardMetaData"))
}
}
}
pub async fn update_merchant_scoped_metadata(
state: &AppState,
user_id: String,
merchant_id: String,
org_id: String,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {
let data_value = serde_json::to_value(metadata_value)
.into_report()
.change_context(UserErrors::InternalServerError)
.attach_printable("Error Converting Struct To Serde Value")?;
state
.store
.update_metadata(
None,
merchant_id,
org_id,
metadata_key,
DashboardMetadataUpdate::UpdateData {
data_key: metadata_key,
data_value,
last_modified_by: user_id,
},
)
.await
.change_context(UserErrors::InternalServerError)
}
pub async fn update_user_scoped_metadata(
state: &AppState,
user_id: String,
merchant_id: String,
org_id: String,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {
let data_value = serde_json::to_value(metadata_value)
.into_report()
.change_context(UserErrors::InternalServerError)
.attach_printable("Error Converting Struct To Serde Value")?;
state
.store
.update_metadata(
Some(user_id.clone()),
merchant_id,
org_id,
metadata_key,
DashboardMetadataUpdate::UpdateData {
data_key: metadata_key,
data_value,
last_modified_by: user_id,
},
)
.await
.change_context(UserErrors::InternalServerError)
}
pub fn deserialize_to_response<T>(data: Option<&DashboardMetadata>) -> UserResult<Option<T>>
where
@ -87,7 +203,7 @@ where
pub fn separate_metadata_type_based_on_scope(
metadata_keys: Vec<DBEnum>,
) -> (Vec<DBEnum>, Vec<DBEnum>) {
let (mut merchant_scoped, user_scoped) = (
let (mut merchant_scoped, mut user_scoped) = (
Vec::with_capacity(metadata_keys.len()),
Vec::with_capacity(metadata_keys.len()),
);
@ -102,6 +218,7 @@ pub fn separate_metadata_type_based_on_scope(
| DBEnum::ConfiguredRouting
| DBEnum::TestPayment
| DBEnum::IntegrationMethod
| DBEnum::ConfigurationType
| DBEnum::IntegrationCompleted
| DBEnum::StripeConnected
| DBEnum::PaypalConnected
@ -111,11 +228,19 @@ pub fn separate_metadata_type_based_on_scope(
| DBEnum::ConfigureWoocom
| DBEnum::SetupWoocomWebhook
| DBEnum::IsMultipleConfiguration => merchant_scoped.push(key),
DBEnum::Feedback | DBEnum::ProdIntent => user_scoped.push(key),
}
}
(merchant_scoped, user_scoped)
}
pub fn is_update_required(metadata: &UserResult<DashboardMetadata>) -> bool {
match metadata {
Ok(_) => false,
Err(e) => matches!(e.current_context(), UserErrors::MetadataAlreadySet),
}
}
pub fn is_backfill_required(metadata_key: &DBEnum) -> bool {
matches!(
metadata_key,