mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(user): add support for dashboard metadata (#3000)
Co-authored-by: Rachit Naithani <81706961+racnan@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com> Co-authored-by: Brian Silah <71752651+unpervertedkid@users.noreply.github.com>
This commit is contained in:
@ -1,6 +1,11 @@
|
||||
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||
|
||||
use crate::user::{ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse};
|
||||
use crate::user::{
|
||||
dashboard_metadata::{
|
||||
GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
|
||||
},
|
||||
ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse,
|
||||
};
|
||||
|
||||
impl ApiEventMetric for ConnectAccountResponse {
|
||||
fn get_api_event_type(&self) -> Option<ApiEventsType> {
|
||||
@ -13,4 +18,10 @@ impl ApiEventMetric for ConnectAccountResponse {
|
||||
|
||||
impl ApiEventMetric for ConnectAccountRequest {}
|
||||
|
||||
common_utils::impl_misc_api_event_type!(ChangePasswordRequest);
|
||||
common_utils::impl_misc_api_event_type!(
|
||||
ChangePasswordRequest,
|
||||
GetMultipleMetaDataPayload,
|
||||
GetMetaDataResponse,
|
||||
GetMetaDataRequest,
|
||||
SetMetaDataRequest
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use common_utils::pii;
|
||||
use masking::Secret;
|
||||
pub mod dashboard_metadata;
|
||||
|
||||
#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
|
||||
pub struct ConnectAccountRequest {
|
||||
|
||||
110
crates/api_models/src/user/dashboard_metadata.rs
Normal file
110
crates/api_models/src/user/dashboard_metadata.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use masking::Secret;
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub enum SetMetaDataRequest {
|
||||
ProductionAgreement(ProductionAgreementRequest),
|
||||
SetupProcessor(SetupProcessor),
|
||||
ConfigureEndpoint,
|
||||
SetupComplete,
|
||||
FirstProcessorConnected(ProcessorConnected),
|
||||
SecondProcessorConnected(ProcessorConnected),
|
||||
ConfiguredRouting(ConfiguredRouting),
|
||||
TestPayment(TestPayment),
|
||||
IntegrationMethod(IntegrationMethod),
|
||||
IntegrationCompleted,
|
||||
SPRoutingConfigured(ConfiguredRouting),
|
||||
SPTestPayment,
|
||||
DownloadWoocom,
|
||||
ConfigureWoocom,
|
||||
SetupWoocomWebhook,
|
||||
IsMultipleConfiguration,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ProductionAgreementRequest {
|
||||
pub version: String,
|
||||
#[serde(skip_deserializing)]
|
||||
pub ip_address: Option<Secret<String, common_utils::pii::IpAddress>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SetupProcessor {
|
||||
pub connector_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ProcessorConnected {
|
||||
pub processor_id: String,
|
||||
pub processor_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ConfiguredRouting {
|
||||
pub routing_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct TestPayment {
|
||||
pub payment_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct IntegrationMethod {
|
||||
pub integration_type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, EnumString, serde::Serialize)]
|
||||
pub enum GetMetaDataRequest {
|
||||
ProductionAgreement,
|
||||
SetupProcessor,
|
||||
ConfigureEndpoint,
|
||||
SetupComplete,
|
||||
FirstProcessorConnected,
|
||||
SecondProcessorConnected,
|
||||
ConfiguredRouting,
|
||||
TestPayment,
|
||||
IntegrationMethod,
|
||||
IntegrationCompleted,
|
||||
StripeConnected,
|
||||
PaypalConnected,
|
||||
SPRoutingConfigured,
|
||||
SPTestPayment,
|
||||
DownloadWoocom,
|
||||
ConfigureWoocom,
|
||||
SetupWoocomWebhook,
|
||||
IsMultipleConfiguration,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct GetMultipleMetaDataPayload {
|
||||
pub results: Vec<GetMetaDataRequest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetMultipleMetaDataRequest {
|
||||
pub keys: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub enum GetMetaDataResponse {
|
||||
ProductionAgreement(bool),
|
||||
SetupProcessor(Option<SetupProcessor>),
|
||||
ConfigureEndpoint(bool),
|
||||
SetupComplete(bool),
|
||||
FirstProcessorConnected(Option<ProcessorConnected>),
|
||||
SecondProcessorConnected(Option<ProcessorConnected>),
|
||||
ConfiguredRouting(Option<ConfiguredRouting>),
|
||||
TestPayment(Option<TestPayment>),
|
||||
IntegrationMethod(Option<IntegrationMethod>),
|
||||
IntegrationCompleted(bool),
|
||||
StripeConnected(Option<ProcessorConnected>),
|
||||
PaypalConnected(Option<ProcessorConnected>),
|
||||
SPRoutingConfigured(Option<ConfiguredRouting>),
|
||||
SPTestPayment(bool),
|
||||
DownloadWoocom(bool),
|
||||
ConfigureWoocom(bool),
|
||||
SetupWoocomWebhook(bool),
|
||||
IsMultipleConfiguration(bool),
|
||||
}
|
||||
@ -425,3 +425,39 @@ pub enum UserStatus {
|
||||
#[default]
|
||||
InvitationSent,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
frunk::LabelledGeneric,
|
||||
)]
|
||||
#[router_derive::diesel_enum(storage_type = "text")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum DashboardMetadata {
|
||||
ProductionAgreement,
|
||||
SetupProcessor,
|
||||
ConfigureEndpoint,
|
||||
SetupComplete,
|
||||
FirstProcessorConnected,
|
||||
SecondProcessorConnected,
|
||||
ConfiguredRouting,
|
||||
TestPayment,
|
||||
IntegrationMethod,
|
||||
IntegrationCompleted,
|
||||
StripeConnected,
|
||||
PaypalConnected,
|
||||
SpRoutingConfigured,
|
||||
SpTestPayment,
|
||||
DownloadWoocom,
|
||||
ConfigureWoocom,
|
||||
SetupWoocomWebhook,
|
||||
IsMultipleConfiguration,
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ pub mod cards_info;
|
||||
pub mod configs;
|
||||
|
||||
pub mod customers;
|
||||
pub mod dashboard_metadata;
|
||||
pub mod dispute;
|
||||
pub mod events;
|
||||
pub mod file;
|
||||
|
||||
64
crates/diesel_models/src/query/dashboard_metadata.rs
Normal file
64
crates/diesel_models/src/query/dashboard_metadata.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
|
||||
use router_env::tracing::{self, instrument};
|
||||
|
||||
use crate::{
|
||||
enums,
|
||||
query::generics,
|
||||
schema::dashboard_metadata::dsl,
|
||||
user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew},
|
||||
PgPooledConn, StorageResult,
|
||||
};
|
||||
|
||||
impl DashboardMetadataNew {
|
||||
#[instrument(skip(conn))]
|
||||
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<DashboardMetadata> {
|
||||
generics::generic_insert(conn, self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl DashboardMetadata {
|
||||
pub async fn find_user_scoped_dashboard_metadata(
|
||||
conn: &PgPooledConn,
|
||||
user_id: String,
|
||||
merchant_id: String,
|
||||
org_id: String,
|
||||
data_types: Vec<enums::DashboardMetadata>,
|
||||
) -> StorageResult<Vec<Self>> {
|
||||
let predicate = dsl::user_id
|
||||
.eq(user_id)
|
||||
.and(dsl::merchant_id.eq(merchant_id))
|
||||
.and(dsl::org_id.eq(org_id))
|
||||
.and(dsl::data_key.eq_any(data_types));
|
||||
|
||||
generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
|
||||
conn,
|
||||
predicate,
|
||||
None,
|
||||
None,
|
||||
Some(dsl::last_modified_at.asc()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_merchant_scoped_dashboard_metadata(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: String,
|
||||
org_id: String,
|
||||
data_types: Vec<enums::DashboardMetadata>,
|
||||
) -> StorageResult<Vec<Self>> {
|
||||
let predicate = dsl::user_id
|
||||
.is_null()
|
||||
.and(dsl::merchant_id.eq(merchant_id))
|
||||
.and(dsl::org_id.eq(org_id))
|
||||
.and(dsl::data_key.eq_any(data_types));
|
||||
|
||||
generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
|
||||
conn,
|
||||
predicate,
|
||||
None,
|
||||
None,
|
||||
Some(dsl::last_modified_at.asc()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -183,6 +183,30 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
dashboard_metadata (id) {
|
||||
id -> Int4,
|
||||
#[max_length = 64]
|
||||
user_id -> Nullable<Varchar>,
|
||||
#[max_length = 64]
|
||||
merchant_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
org_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
data_key -> Varchar,
|
||||
data_value -> Json,
|
||||
#[max_length = 64]
|
||||
created_by -> Varchar,
|
||||
created_at -> Timestamp,
|
||||
#[max_length = 64]
|
||||
last_modified_by -> Varchar,
|
||||
last_modified_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
@ -965,6 +989,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
cards_info,
|
||||
configs,
|
||||
customers,
|
||||
dashboard_metadata,
|
||||
dispute,
|
||||
events,
|
||||
file_metadata,
|
||||
|
||||
@ -5,6 +5,8 @@ use time::PrimitiveDateTime;
|
||||
|
||||
use crate::schema::users;
|
||||
|
||||
pub mod dashboard_metadata;
|
||||
|
||||
#[derive(Clone, Debug, Identifiable, Queryable)]
|
||||
#[diesel(table_name = users)]
|
||||
pub struct User {
|
||||
|
||||
35
crates/diesel_models/src/user/dashboard_metadata.rs
Normal file
35
crates/diesel_models/src/user/dashboard_metadata.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use diesel::{query_builder::AsChangeset, Identifiable, Insertable, Queryable};
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::{enums, schema::dashboard_metadata};
|
||||
|
||||
#[derive(Clone, Debug, Identifiable, Queryable)]
|
||||
#[diesel(table_name = dashboard_metadata)]
|
||||
pub struct DashboardMetadata {
|
||||
pub id: i32,
|
||||
pub user_id: Option<String>,
|
||||
pub merchant_id: String,
|
||||
pub org_id: String,
|
||||
pub data_key: enums::DashboardMetadata,
|
||||
pub data_value: serde_json::Value,
|
||||
pub created_by: String,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_by: String,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay, AsChangeset,
|
||||
)]
|
||||
#[diesel(table_name = dashboard_metadata)]
|
||||
pub struct DashboardMetadataNew {
|
||||
pub user_id: Option<String>,
|
||||
pub merchant_id: String,
|
||||
pub org_id: String,
|
||||
pub data_key: enums::DashboardMetadata,
|
||||
pub data_value: serde_json::Value,
|
||||
pub created_by: String,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_by: String,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
@ -27,10 +27,16 @@ pub enum UserErrors {
|
||||
MerchantAccountCreationError(String),
|
||||
#[error("InvalidEmailError")]
|
||||
InvalidEmailError,
|
||||
#[error("DuplicateOrganizationId")]
|
||||
DuplicateOrganizationId,
|
||||
#[error("MerchantIdNotFound")]
|
||||
MerchantIdNotFound,
|
||||
#[error("MetadataAlreadySet")]
|
||||
MetadataAlreadySet,
|
||||
#[error("DuplicateOrganizationId")]
|
||||
DuplicateOrganizationId,
|
||||
#[error("IpAddressParsingFailed")]
|
||||
IpAddressParsingFailed,
|
||||
#[error("InvalidMetadataRequest")]
|
||||
InvalidMetadataRequest,
|
||||
}
|
||||
|
||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||
@ -77,15 +83,27 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::InvalidEmailError => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 16, "Invalid Email", None))
|
||||
}
|
||||
Self::MerchantIdNotFound => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 18, "Invalid Merchant ID", None))
|
||||
}
|
||||
Self::MetadataAlreadySet => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 19, "Metadata already set", None))
|
||||
}
|
||||
Self::DuplicateOrganizationId => AER::InternalServerError(ApiError::new(
|
||||
sub_code,
|
||||
21,
|
||||
"An Organization with the id already exists",
|
||||
None,
|
||||
)),
|
||||
Self::MerchantIdNotFound => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 18, "Invalid Merchant ID", None))
|
||||
Self::IpAddressParsingFailed => {
|
||||
AER::InternalServerError(ApiError::new(sub_code, 24, "Something Went Wrong", None))
|
||||
}
|
||||
Self::InvalidMetadataRequest => AER::BadRequest(ApiError::new(
|
||||
sub_code,
|
||||
26,
|
||||
"Invalid Metadata Request",
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ use crate::{
|
||||
types::domain,
|
||||
};
|
||||
|
||||
pub mod dashboard_metadata;
|
||||
|
||||
pub async fn connect_account(
|
||||
state: AppState,
|
||||
request: api::ConnectAccountRequest,
|
||||
|
||||
537
crates/router/src/core/user/dashboard_metadata.rs
Normal file
537
crates/router/src/core/user/dashboard_metadata.rs
Normal file
@ -0,0 +1,537 @@
|
||||
use api_models::user::dashboard_metadata::{self as api, GetMultipleMetaDataPayload};
|
||||
use diesel_models::{
|
||||
enums::DashboardMetadata as DBEnum, user::dashboard_metadata::DashboardMetadata,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use crate::{
|
||||
core::errors::{UserErrors, UserResponse, UserResult},
|
||||
routes::AppState,
|
||||
services::{authentication::UserFromToken, ApplicationResponse},
|
||||
types::domain::{user::dashboard_metadata as types, MerchantKeyStore},
|
||||
utils::user::dashboard_metadata as utils,
|
||||
};
|
||||
|
||||
pub async fn set_metadata(
|
||||
state: AppState,
|
||||
user: UserFromToken,
|
||||
request: api::SetMetaDataRequest,
|
||||
) -> UserResponse<()> {
|
||||
let metadata_value = parse_set_request(request)?;
|
||||
let metadata_key = DBEnum::from(&metadata_value);
|
||||
|
||||
insert_metadata(&state, user, metadata_key, metadata_value).await?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn get_multiple_metadata(
|
||||
state: AppState,
|
||||
user: UserFromToken,
|
||||
request: GetMultipleMetaDataPayload,
|
||||
) -> UserResponse<Vec<api::GetMetaDataResponse>> {
|
||||
let metadata_keys: Vec<DBEnum> = request.results.into_iter().map(parse_get_request).collect();
|
||||
|
||||
let metadata = fetch_metadata(&state, &user, metadata_keys.clone()).await?;
|
||||
|
||||
let mut response = Vec::with_capacity(metadata_keys.len());
|
||||
for key in metadata_keys {
|
||||
let data = metadata.iter().find(|ele| ele.data_key == key);
|
||||
let resp;
|
||||
if data.is_none() && utils::is_backfill_required(&key) {
|
||||
let backfill_data = backfill_metadata(&state, &user, &key).await?;
|
||||
resp = into_response(backfill_data.as_ref(), &key)?;
|
||||
} else {
|
||||
resp = into_response(data, &key)?;
|
||||
}
|
||||
response.push(resp);
|
||||
}
|
||||
|
||||
Ok(ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
fn parse_set_request(data_enum: api::SetMetaDataRequest) -> UserResult<types::MetaData> {
|
||||
match data_enum {
|
||||
api::SetMetaDataRequest::ProductionAgreement(req) => {
|
||||
let ip_address = req
|
||||
.ip_address
|
||||
.ok_or(UserErrors::InternalServerError.into())
|
||||
.attach_printable("Error Getting Ip Address")?;
|
||||
Ok(types::MetaData::ProductionAgreement(
|
||||
types::ProductionAgreementValue {
|
||||
version: req.version,
|
||||
ip_address,
|
||||
timestamp: common_utils::date_time::now(),
|
||||
},
|
||||
))
|
||||
}
|
||||
api::SetMetaDataRequest::SetupProcessor(req) => Ok(types::MetaData::SetupProcessor(req)),
|
||||
api::SetMetaDataRequest::ConfigureEndpoint => Ok(types::MetaData::ConfigureEndpoint(true)),
|
||||
api::SetMetaDataRequest::SetupComplete => Ok(types::MetaData::SetupComplete(true)),
|
||||
api::SetMetaDataRequest::FirstProcessorConnected(req) => {
|
||||
Ok(types::MetaData::FirstProcessorConnected(req))
|
||||
}
|
||||
api::SetMetaDataRequest::SecondProcessorConnected(req) => {
|
||||
Ok(types::MetaData::SecondProcessorConnected(req))
|
||||
}
|
||||
api::SetMetaDataRequest::ConfiguredRouting(req) => {
|
||||
Ok(types::MetaData::ConfiguredRouting(req))
|
||||
}
|
||||
api::SetMetaDataRequest::TestPayment(req) => Ok(types::MetaData::TestPayment(req)),
|
||||
api::SetMetaDataRequest::IntegrationMethod(req) => {
|
||||
Ok(types::MetaData::IntegrationMethod(req))
|
||||
}
|
||||
api::SetMetaDataRequest::IntegrationCompleted => {
|
||||
Ok(types::MetaData::IntegrationCompleted(true))
|
||||
}
|
||||
api::SetMetaDataRequest::SPRoutingConfigured(req) => {
|
||||
Ok(types::MetaData::SPRoutingConfigured(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)),
|
||||
api::SetMetaDataRequest::SetupWoocomWebhook => {
|
||||
Ok(types::MetaData::SetupWoocomWebhook(true))
|
||||
}
|
||||
api::SetMetaDataRequest::IsMultipleConfiguration => {
|
||||
Ok(types::MetaData::IsMultipleConfiguration(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_get_request(data_enum: api::GetMetaDataRequest) -> DBEnum {
|
||||
match data_enum {
|
||||
api::GetMetaDataRequest::ProductionAgreement => DBEnum::ProductionAgreement,
|
||||
api::GetMetaDataRequest::SetupProcessor => DBEnum::SetupProcessor,
|
||||
api::GetMetaDataRequest::ConfigureEndpoint => DBEnum::ConfigureEndpoint,
|
||||
api::GetMetaDataRequest::SetupComplete => DBEnum::SetupComplete,
|
||||
api::GetMetaDataRequest::FirstProcessorConnected => DBEnum::FirstProcessorConnected,
|
||||
api::GetMetaDataRequest::SecondProcessorConnected => DBEnum::SecondProcessorConnected,
|
||||
api::GetMetaDataRequest::ConfiguredRouting => DBEnum::ConfiguredRouting,
|
||||
api::GetMetaDataRequest::TestPayment => DBEnum::TestPayment,
|
||||
api::GetMetaDataRequest::IntegrationMethod => DBEnum::IntegrationMethod,
|
||||
api::GetMetaDataRequest::IntegrationCompleted => DBEnum::IntegrationCompleted,
|
||||
api::GetMetaDataRequest::StripeConnected => DBEnum::StripeConnected,
|
||||
api::GetMetaDataRequest::PaypalConnected => DBEnum::PaypalConnected,
|
||||
api::GetMetaDataRequest::SPRoutingConfigured => DBEnum::SpRoutingConfigured,
|
||||
api::GetMetaDataRequest::SPTestPayment => DBEnum::SpTestPayment,
|
||||
api::GetMetaDataRequest::DownloadWoocom => DBEnum::DownloadWoocom,
|
||||
api::GetMetaDataRequest::ConfigureWoocom => DBEnum::ConfigureWoocom,
|
||||
api::GetMetaDataRequest::SetupWoocomWebhook => DBEnum::SetupWoocomWebhook,
|
||||
api::GetMetaDataRequest::IsMultipleConfiguration => DBEnum::IsMultipleConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_response(
|
||||
data: Option<&DashboardMetadata>,
|
||||
data_type: &DBEnum,
|
||||
) -> UserResult<api::GetMetaDataResponse> {
|
||||
match data_type {
|
||||
DBEnum::ProductionAgreement => Ok(api::GetMetaDataResponse::ProductionAgreement(
|
||||
data.is_some(),
|
||||
)),
|
||||
DBEnum::SetupProcessor => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::SetupProcessor(resp))
|
||||
}
|
||||
DBEnum::ConfigureEndpoint => {
|
||||
Ok(api::GetMetaDataResponse::ConfigureEndpoint(data.is_some()))
|
||||
}
|
||||
DBEnum::SetupComplete => Ok(api::GetMetaDataResponse::SetupComplete(data.is_some())),
|
||||
DBEnum::FirstProcessorConnected => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::FirstProcessorConnected(resp))
|
||||
}
|
||||
DBEnum::SecondProcessorConnected => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::SecondProcessorConnected(resp))
|
||||
}
|
||||
DBEnum::ConfiguredRouting => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::ConfiguredRouting(resp))
|
||||
}
|
||||
DBEnum::TestPayment => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::TestPayment(resp))
|
||||
}
|
||||
DBEnum::IntegrationMethod => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::IntegrationMethod(resp))
|
||||
}
|
||||
DBEnum::IntegrationCompleted => Ok(api::GetMetaDataResponse::IntegrationCompleted(
|
||||
data.is_some(),
|
||||
)),
|
||||
DBEnum::StripeConnected => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::StripeConnected(resp))
|
||||
}
|
||||
DBEnum::PaypalConnected => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::PaypalConnected(resp))
|
||||
}
|
||||
DBEnum::SpRoutingConfigured => {
|
||||
let resp = utils::deserialize_to_response(data)?;
|
||||
Ok(api::GetMetaDataResponse::SPRoutingConfigured(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())),
|
||||
DBEnum::SetupWoocomWebhook => {
|
||||
Ok(api::GetMetaDataResponse::SetupWoocomWebhook(data.is_some()))
|
||||
}
|
||||
|
||||
DBEnum::IsMultipleConfiguration => Ok(api::GetMetaDataResponse::IsMultipleConfiguration(
|
||||
data.is_some(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert_metadata(
|
||||
state: &AppState,
|
||||
user: UserFromToken,
|
||||
metadata_key: DBEnum,
|
||||
metadata_value: types::MetaData,
|
||||
) -> UserResult<DashboardMetadata> {
|
||||
match metadata_value {
|
||||
types::MetaData::ProductionAgreement(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::SetupProcessor(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::ConfigureEndpoint(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::SetupComplete(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::FirstProcessorConnected(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::SecondProcessorConnected(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::ConfiguredRouting(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::TestPayment(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::IntegrationMethod(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::IntegrationCompleted(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::StripeConnected(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::PaypalConnected(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::SPRoutingConfigured(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::SPTestPayment(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::DownloadWoocom(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::ConfigureWoocom(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::SetupWoocomWebhook(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
types::MetaData::IsMultipleConfiguration(data) => {
|
||||
utils::insert_merchant_scoped_metadata_to_db(
|
||||
state,
|
||||
user.user_id,
|
||||
user.merchant_id,
|
||||
user.org_id,
|
||||
metadata_key,
|
||||
data,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_metadata(
|
||||
state: &AppState,
|
||||
user: &UserFromToken,
|
||||
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);
|
||||
|
||||
if !merchant_scoped_enums.is_empty() {
|
||||
let mut res = utils::get_merchant_scoped_metadata_from_db(
|
||||
state,
|
||||
user.merchant_id.to_owned(),
|
||||
user.org_id.to_owned(),
|
||||
merchant_scoped_enums,
|
||||
)
|
||||
.await?;
|
||||
dashboard_metadata.append(&mut res);
|
||||
}
|
||||
|
||||
Ok(dashboard_metadata)
|
||||
}
|
||||
|
||||
pub async fn backfill_metadata(
|
||||
state: &AppState,
|
||||
user: &UserFromToken,
|
||||
key: &DBEnum,
|
||||
) -> UserResult<Option<DashboardMetadata>> {
|
||||
let key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
&user.merchant_id,
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
match key {
|
||||
DBEnum::StripeConnected => {
|
||||
let mca = if let Some(stripe_connected) = get_merchant_connector_account_by_name(
|
||||
state,
|
||||
&user.merchant_id,
|
||||
api_models::enums::RoutableConnectors::Stripe
|
||||
.to_string()
|
||||
.as_str(),
|
||||
&key_store,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
stripe_connected
|
||||
} else if let Some(stripe_test_connected) = get_merchant_connector_account_by_name(
|
||||
state,
|
||||
&user.merchant_id,
|
||||
//TODO: Use Enum with proper feature flag
|
||||
"stripe_test",
|
||||
&key_store,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
stripe_test_connected
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Some(
|
||||
insert_metadata(
|
||||
state,
|
||||
user.to_owned(),
|
||||
DBEnum::StripeConnected,
|
||||
types::MetaData::StripeConnected(api::ProcessorConnected {
|
||||
processor_id: mca.merchant_connector_id,
|
||||
processor_name: mca.connector_name,
|
||||
}),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.transpose()
|
||||
}
|
||||
DBEnum::PaypalConnected => {
|
||||
let mca = if let Some(paypal_connected) = get_merchant_connector_account_by_name(
|
||||
state,
|
||||
&user.merchant_id,
|
||||
api_models::enums::RoutableConnectors::Paypal
|
||||
.to_string()
|
||||
.as_str(),
|
||||
&key_store,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
paypal_connected
|
||||
} else if let Some(paypal_test_connected) = get_merchant_connector_account_by_name(
|
||||
state,
|
||||
&user.merchant_id,
|
||||
//TODO: Use Enum with proper feature flag
|
||||
"paypal_test",
|
||||
&key_store,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
paypal_test_connected
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Some(
|
||||
insert_metadata(
|
||||
state,
|
||||
user.to_owned(),
|
||||
DBEnum::PaypalConnected,
|
||||
types::MetaData::PaypalConnected(api::ProcessorConnected {
|
||||
processor_id: mca.merchant_connector_id,
|
||||
processor_name: mca.connector_name,
|
||||
}),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.transpose()
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_merchant_connector_account_by_name(
|
||||
state: &AppState,
|
||||
merchant_id: &str,
|
||||
connector_name: &str,
|
||||
key_store: &MerchantKeyStore,
|
||||
) -> UserResult<Option<crate::types::domain::MerchantConnectorAccount>> {
|
||||
state
|
||||
.store
|
||||
.find_merchant_connector_account_by_merchant_id_connector_name(
|
||||
merchant_id,
|
||||
connector_name,
|
||||
key_store,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
e.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("DB Error Fetching DashboardMetaData")
|
||||
})
|
||||
.map(|data| data.first().cloned())
|
||||
}
|
||||
@ -6,6 +6,7 @@ pub mod capture;
|
||||
pub mod cards_info;
|
||||
pub mod configs;
|
||||
pub mod customers;
|
||||
pub mod dashboard_metadata;
|
||||
pub mod dispute;
|
||||
pub mod ephemeral_key;
|
||||
pub mod events;
|
||||
@ -68,6 +69,7 @@ pub trait StorageInterface:
|
||||
+ configs::ConfigInterface
|
||||
+ capture::CaptureInterface
|
||||
+ customers::CustomerInterface
|
||||
+ dashboard_metadata::DashboardMetadataInterface
|
||||
+ dispute::DisputeInterface
|
||||
+ ephemeral_key::EphemeralKeyInterface
|
||||
+ events::EventInterface
|
||||
|
||||
184
crates/router/src/db/dashboard_metadata.rs
Normal file
184
crates/router/src/db/dashboard_metadata.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use diesel_models::{enums, user::dashboard_metadata as storage};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use storage_impl::MockDb;
|
||||
|
||||
use crate::{
|
||||
connection,
|
||||
core::errors::{self, CustomResult},
|
||||
services::Store,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait DashboardMetadataInterface {
|
||||
async fn insert_metadata(
|
||||
&self,
|
||||
metadata: storage::DashboardMetadataNew,
|
||||
) -> CustomResult<storage::DashboardMetadata, errors::StorageError>;
|
||||
|
||||
async fn find_user_scoped_dashboard_metadata(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError>;
|
||||
async fn find_merchant_scoped_dashboard_metadata(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DashboardMetadataInterface for Store {
|
||||
async fn insert_metadata(
|
||||
&self,
|
||||
metadata: storage::DashboardMetadataNew,
|
||||
) -> CustomResult<storage::DashboardMetadata, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
metadata
|
||||
.insert(&conn)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn find_user_scoped_dashboard_metadata(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::DashboardMetadata::find_user_scoped_dashboard_metadata(
|
||||
&conn,
|
||||
user_id.to_owned(),
|
||||
merchant_id.to_owned(),
|
||||
org_id.to_owned(),
|
||||
data_keys,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn find_merchant_scoped_dashboard_metadata(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::DashboardMetadata::find_merchant_scoped_dashboard_metadata(
|
||||
&conn,
|
||||
merchant_id.to_owned(),
|
||||
org_id.to_owned(),
|
||||
data_keys,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DashboardMetadataInterface for MockDb {
|
||||
async fn insert_metadata(
|
||||
&self,
|
||||
metadata: storage::DashboardMetadataNew,
|
||||
) -> CustomResult<storage::DashboardMetadata, errors::StorageError> {
|
||||
let mut dashboard_metadata = self.dashboard_metadata.lock().await;
|
||||
if dashboard_metadata.iter().any(|metadata_inner| {
|
||||
metadata_inner.user_id == metadata.user_id
|
||||
&& metadata_inner.merchant_id == metadata.merchant_id
|
||||
&& metadata_inner.org_id == metadata.org_id
|
||||
&& metadata_inner.data_key == metadata.data_key
|
||||
}) {
|
||||
Err(errors::StorageError::DuplicateValue {
|
||||
entity: "user_id, merchant_id, org_id and data_key",
|
||||
key: None,
|
||||
})?
|
||||
}
|
||||
let metadata_new = storage::DashboardMetadata {
|
||||
id: dashboard_metadata
|
||||
.len()
|
||||
.try_into()
|
||||
.into_report()
|
||||
.change_context(errors::StorageError::MockDbError)?,
|
||||
user_id: metadata.user_id,
|
||||
merchant_id: metadata.merchant_id,
|
||||
org_id: metadata.org_id,
|
||||
data_key: metadata.data_key,
|
||||
data_value: metadata.data_value,
|
||||
created_by: metadata.created_by,
|
||||
created_at: metadata.created_at,
|
||||
last_modified_by: metadata.last_modified_by,
|
||||
last_modified_at: metadata.last_modified_at,
|
||||
};
|
||||
dashboard_metadata.push(metadata_new.clone());
|
||||
Ok(metadata_new)
|
||||
}
|
||||
|
||||
async fn find_user_scoped_dashboard_metadata(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
|
||||
let dashboard_metadata = self.dashboard_metadata.lock().await;
|
||||
let query_result = dashboard_metadata
|
||||
.iter()
|
||||
.filter(|metadata_inner| {
|
||||
metadata_inner
|
||||
.user_id
|
||||
.clone()
|
||||
.map(|user_id_inner| user_id_inner == user_id)
|
||||
.unwrap_or(false)
|
||||
&& metadata_inner.merchant_id == merchant_id
|
||||
&& metadata_inner.org_id == org_id
|
||||
&& data_keys.contains(&metadata_inner.data_key)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<storage::DashboardMetadata>>();
|
||||
|
||||
if query_result.is_empty() {
|
||||
return Err(errors::StorageError::ValueNotFound(format!(
|
||||
"No dashboard_metadata available for user_id = {user_id},\
|
||||
merchant_id = {merchant_id}, org_id = {org_id} and data_keys = {data_keys:?}",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
Ok(query_result)
|
||||
}
|
||||
|
||||
async fn find_merchant_scoped_dashboard_metadata(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
|
||||
let dashboard_metadata = self.dashboard_metadata.lock().await;
|
||||
let query_result = dashboard_metadata
|
||||
.iter()
|
||||
.filter(|metadata_inner| {
|
||||
metadata_inner.user_id.is_none()
|
||||
&& metadata_inner.merchant_id == merchant_id
|
||||
&& metadata_inner.org_id == org_id
|
||||
&& data_keys.contains(&metadata_inner.data_key)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<storage::DashboardMetadata>>();
|
||||
|
||||
if query_result.is_empty() {
|
||||
return Err(errors::StorageError::ValueNotFound(format!(
|
||||
"No dashboard_metadata available for merchant_id = {merchant_id},\
|
||||
org_id = {org_id} and data_keyss = {data_keys:?}",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
Ok(query_result)
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ use data_models::payments::{
|
||||
payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface,
|
||||
};
|
||||
use diesel_models::{
|
||||
enums,
|
||||
enums::ProcessTrackerStatus,
|
||||
ephemeral_key::{EphemeralKey, EphemeralKeyNew},
|
||||
reverse_lookup::{ReverseLookup, ReverseLookupNew},
|
||||
@ -21,7 +22,10 @@ use scheduler::{
|
||||
use storage_impl::redis::kv_store::RedisConnInterface;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use super::{user::UserInterface, user_role::UserRoleInterface};
|
||||
use super::{
|
||||
dashboard_metadata::DashboardMetadataInterface, user::UserInterface,
|
||||
user_role::UserRoleInterface,
|
||||
};
|
||||
use crate::{
|
||||
core::errors::{self, ProcessTrackerError},
|
||||
db::{
|
||||
@ -1915,3 +1919,35 @@ impl UserRoleInterface for KafkaStore {
|
||||
self.diesel_store.list_user_roles_by_user_id(user_id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DashboardMetadataInterface for KafkaStore {
|
||||
async fn insert_metadata(
|
||||
&self,
|
||||
metadata: storage::DashboardMetadataNew,
|
||||
) -> CustomResult<storage::DashboardMetadata, errors::StorageError> {
|
||||
self.diesel_store.insert_metadata(metadata).await
|
||||
}
|
||||
|
||||
async fn find_user_scoped_dashboard_metadata(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.find_user_scoped_dashboard_metadata(user_id, merchant_id, org_id, data_keys)
|
||||
.await
|
||||
}
|
||||
async fn find_merchant_scoped_dashboard_metadata(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
data_keys: Vec<enums::DashboardMetadata>,
|
||||
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.find_merchant_scoped_dashboard_metadata(merchant_id, org_id, data_keys)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,6 +807,11 @@ impl User {
|
||||
.service(web::resource("/v2/signin").route(web::post().to(user_connect_account)))
|
||||
.service(web::resource("/v2/signup").route(web::post().to(user_connect_account)))
|
||||
.service(web::resource("/change_password").route(web::post().to(change_password)))
|
||||
.service(
|
||||
web::resource("/data/merchant")
|
||||
.route(web::post().to(set_merchant_scoped_dashboard_metadata)),
|
||||
)
|
||||
.service(web::resource("/data").route(web::get().to(get_multiple_dashboard_metadata)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -147,9 +147,11 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::GsmRuleUpdate
|
||||
| Flow::GsmRuleDelete => Self::Gsm,
|
||||
|
||||
Flow::UserConnectAccount | Flow::ChangePassword | Flow::VerifyPaymentConnector => {
|
||||
Self::User
|
||||
}
|
||||
Flow::UserConnectAccount
|
||||
| Flow::ChangePassword
|
||||
| Flow::SetDashboardMetadata
|
||||
| Flow::GetMutltipleDashboardMetadata
|
||||
| Flow::VerifyPaymentConnector => Self::User,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use api_models::user as user_api;
|
||||
use api_models::{errors::types::ApiErrorResponse, user as user_api};
|
||||
use common_utils::errors::ReportSwitchExt;
|
||||
use router_env::Flow;
|
||||
|
||||
use super::AppState;
|
||||
@ -8,7 +9,9 @@ use crate::{
|
||||
services::{
|
||||
api,
|
||||
authentication::{self as auth},
|
||||
authorization::permissions::Permission,
|
||||
},
|
||||
utils::user::dashboard_metadata::{parse_string_to_enums, set_ip_address_if_required},
|
||||
};
|
||||
|
||||
pub async fn user_connect_account(
|
||||
@ -47,3 +50,55 @@ pub async fn change_password(
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_merchant_scoped_dashboard_metadata(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<user_api::dashboard_metadata::SetMetaDataRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::SetDashboardMetadata;
|
||||
let mut payload = json_payload.into_inner();
|
||||
|
||||
if let Err(e) = common_utils::errors::ReportSwitchExt::<(), ApiErrorResponse>::switch(
|
||||
set_ip_address_if_required(&mut payload, req.headers()),
|
||||
) {
|
||||
return api::log_and_return_error_response(e);
|
||||
}
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
user::dashboard_metadata::set_metadata,
|
||||
&auth::JWTAuth(Permission::MerchantAccountWrite),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_multiple_dashboard_metadata(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
query: web::Query<user_api::dashboard_metadata::GetMultipleMetaDataRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::GetMutltipleDashboardMetadata;
|
||||
let payload = match ReportSwitchExt::<_, ApiErrorResponse>::switch(parse_string_to_enums(
|
||||
query.into_inner().keys,
|
||||
)) {
|
||||
Ok(payload) => payload,
|
||||
Err(e) => {
|
||||
return api::log_and_return_error_response(e);
|
||||
}
|
||||
};
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
user::dashboard_metadata::get_multiple_metadata,
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@ use crate::{
|
||||
utils::user::password,
|
||||
};
|
||||
|
||||
pub mod dashboard_metadata;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UserName(Secret<String>);
|
||||
|
||||
|
||||
56
crates/router/src/types/domain/user/dashboard_metadata.rs
Normal file
56
crates/router/src/types/domain/user/dashboard_metadata.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use api_models::user::dashboard_metadata as api;
|
||||
use diesel_models::enums::DashboardMetadata as DBEnum;
|
||||
use masking::Secret;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
pub enum MetaData {
|
||||
ProductionAgreement(ProductionAgreementValue),
|
||||
SetupProcessor(api::SetupProcessor),
|
||||
ConfigureEndpoint(bool),
|
||||
SetupComplete(bool),
|
||||
FirstProcessorConnected(api::ProcessorConnected),
|
||||
SecondProcessorConnected(api::ProcessorConnected),
|
||||
ConfiguredRouting(api::ConfiguredRouting),
|
||||
TestPayment(api::TestPayment),
|
||||
IntegrationMethod(api::IntegrationMethod),
|
||||
IntegrationCompleted(bool),
|
||||
StripeConnected(api::ProcessorConnected),
|
||||
PaypalConnected(api::ProcessorConnected),
|
||||
SPRoutingConfigured(api::ConfiguredRouting),
|
||||
SPTestPayment(bool),
|
||||
DownloadWoocom(bool),
|
||||
ConfigureWoocom(bool),
|
||||
SetupWoocomWebhook(bool),
|
||||
IsMultipleConfiguration(bool),
|
||||
}
|
||||
|
||||
impl From<&MetaData> for DBEnum {
|
||||
fn from(value: &MetaData) -> Self {
|
||||
match value {
|
||||
MetaData::ProductionAgreement(_) => Self::ProductionAgreement,
|
||||
MetaData::SetupProcessor(_) => Self::SetupProcessor,
|
||||
MetaData::ConfigureEndpoint(_) => Self::ConfigureEndpoint,
|
||||
MetaData::SetupComplete(_) => Self::SetupComplete,
|
||||
MetaData::FirstProcessorConnected(_) => Self::FirstProcessorConnected,
|
||||
MetaData::SecondProcessorConnected(_) => Self::SecondProcessorConnected,
|
||||
MetaData::ConfiguredRouting(_) => Self::ConfiguredRouting,
|
||||
MetaData::TestPayment(_) => Self::TestPayment,
|
||||
MetaData::IntegrationMethod(_) => Self::IntegrationMethod,
|
||||
MetaData::IntegrationCompleted(_) => Self::IntegrationCompleted,
|
||||
MetaData::StripeConnected(_) => Self::StripeConnected,
|
||||
MetaData::PaypalConnected(_) => Self::PaypalConnected,
|
||||
MetaData::SPRoutingConfigured(_) => Self::SpRoutingConfigured,
|
||||
MetaData::SPTestPayment(_) => Self::SpTestPayment,
|
||||
MetaData::DownloadWoocom(_) => Self::DownloadWoocom,
|
||||
MetaData::ConfigureWoocom(_) => Self::ConfigureWoocom,
|
||||
MetaData::SetupWoocomWebhook(_) => Self::SetupWoocomWebhook,
|
||||
MetaData::IsMultipleConfiguration(_) => Self::IsMultipleConfiguration,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ProductionAgreementValue {
|
||||
pub version: String,
|
||||
pub ip_address: Secret<String, common_utils::pii::IpAddress>,
|
||||
pub timestamp: PrimitiveDateTime,
|
||||
}
|
||||
@ -5,6 +5,7 @@ pub mod capture;
|
||||
pub mod cards_info;
|
||||
pub mod configs;
|
||||
pub mod customers;
|
||||
pub mod dashboard_metadata;
|
||||
pub mod dispute;
|
||||
pub mod enums;
|
||||
pub mod ephemeral_key;
|
||||
@ -42,11 +43,11 @@ pub use data_models::payments::{
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
address::*, api_keys::*, capture::*, cards_info::*, configs::*, customers::*, dispute::*,
|
||||
ephemeral_key::*, events::*, file::*, gsm::*, locker_mock_up::*, mandate::*,
|
||||
merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_link::*,
|
||||
payment_method::*, payout_attempt::*, payouts::*, process_tracker::*, refund::*,
|
||||
reverse_lookup::*, routing_algorithm::*, user::*, user_role::*,
|
||||
address::*, api_keys::*, capture::*, cards_info::*, configs::*, customers::*,
|
||||
dashboard_metadata::*, dispute::*, ephemeral_key::*, events::*, file::*, gsm::*,
|
||||
locker_mock_up::*, mandate::*, merchant_account::*, merchant_connector_account::*,
|
||||
merchant_key_store::*, payment_link::*, payment_method::*, payout_attempt::*, payouts::*,
|
||||
process_tracker::*, refund::*, reverse_lookup::*, routing_algorithm::*, user::*, user_role::*,
|
||||
};
|
||||
use crate::types::api::routing;
|
||||
|
||||
|
||||
1
crates/router/src/types/storage/dashboard_metadata.rs
Normal file
1
crates/router/src/types/storage/dashboard_metadata.rs
Normal file
@ -0,0 +1 @@
|
||||
pub use diesel_models::user::dashboard_metadata::*;
|
||||
@ -1 +1,2 @@
|
||||
pub mod dashboard_metadata;
|
||||
pub mod password;
|
||||
|
||||
162
crates/router/src/utils/user/dashboard_metadata.rs
Normal file
162
crates/router/src/utils/user/dashboard_metadata.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
|
||||
use actix_web::http::header::HeaderMap;
|
||||
use api_models::user::dashboard_metadata::{
|
||||
GetMetaDataRequest, GetMultipleMetaDataPayload, SetMetaDataRequest,
|
||||
};
|
||||
use diesel_models::{
|
||||
enums::DashboardMetadata as DBEnum,
|
||||
user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew},
|
||||
};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use masking::Secret;
|
||||
|
||||
use crate::{
|
||||
core::errors::{UserErrors, UserResult},
|
||||
headers, AppState,
|
||||
};
|
||||
|
||||
pub async fn insert_merchant_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: None,
|
||||
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,
|
||||
merchant_id: String,
|
||||
org_id: String,
|
||||
metadata_keys: Vec<DBEnum>,
|
||||
) -> UserResult<Vec<DashboardMetadata>> {
|
||||
match state
|
||||
.store
|
||||
.find_merchant_scoped_dashboard_metadata(&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 fn deserialize_to_response<T>(data: Option<&DashboardMetadata>) -> UserResult<Option<T>>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
data.map(|metadata| serde_json::from_value(metadata.data_value.clone()))
|
||||
.transpose()
|
||||
.map_err(|_| UserErrors::InternalServerError.into())
|
||||
.attach_printable("Error Serializing Metadata from DB")
|
||||
}
|
||||
|
||||
pub fn separate_metadata_type_based_on_scope(
|
||||
metadata_keys: Vec<DBEnum>,
|
||||
) -> (Vec<DBEnum>, Vec<DBEnum>) {
|
||||
let (mut merchant_scoped, user_scoped) = (
|
||||
Vec::with_capacity(metadata_keys.len()),
|
||||
Vec::with_capacity(metadata_keys.len()),
|
||||
);
|
||||
for key in metadata_keys {
|
||||
match key {
|
||||
DBEnum::ProductionAgreement
|
||||
| DBEnum::SetupProcessor
|
||||
| DBEnum::ConfigureEndpoint
|
||||
| DBEnum::SetupComplete
|
||||
| DBEnum::FirstProcessorConnected
|
||||
| DBEnum::SecondProcessorConnected
|
||||
| DBEnum::ConfiguredRouting
|
||||
| DBEnum::TestPayment
|
||||
| DBEnum::IntegrationMethod
|
||||
| DBEnum::IntegrationCompleted
|
||||
| DBEnum::StripeConnected
|
||||
| DBEnum::PaypalConnected
|
||||
| DBEnum::SpRoutingConfigured
|
||||
| DBEnum::SpTestPayment
|
||||
| DBEnum::DownloadWoocom
|
||||
| DBEnum::ConfigureWoocom
|
||||
| DBEnum::SetupWoocomWebhook
|
||||
| DBEnum::IsMultipleConfiguration => merchant_scoped.push(key),
|
||||
}
|
||||
}
|
||||
(merchant_scoped, user_scoped)
|
||||
}
|
||||
|
||||
pub fn is_backfill_required(metadata_key: &DBEnum) -> bool {
|
||||
matches!(
|
||||
metadata_key,
|
||||
DBEnum::StripeConnected | DBEnum::PaypalConnected
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_ip_address_if_required(
|
||||
request: &mut SetMetaDataRequest,
|
||||
headers: &HeaderMap,
|
||||
) -> UserResult<()> {
|
||||
if let SetMetaDataRequest::ProductionAgreement(req) = request {
|
||||
let ip_address_from_request: Secret<String, common_utils::pii::IpAddress> = headers
|
||||
.get(headers::X_FORWARDED_FOR)
|
||||
.ok_or(UserErrors::IpAddressParsingFailed.into())
|
||||
.attach_printable("X-Forwarded-For header not found")?
|
||||
.to_str()
|
||||
.map_err(|_| UserErrors::IpAddressParsingFailed.into())
|
||||
.attach_printable("Error converting Header Value to Str")?
|
||||
.split(',')
|
||||
.next()
|
||||
.and_then(|ip| {
|
||||
let ip_addr: Result<IpAddr, _> = ip.parse();
|
||||
ip_addr.ok()
|
||||
})
|
||||
.ok_or(UserErrors::IpAddressParsingFailed.into())
|
||||
.attach_printable("Error Parsing header value to ip")?
|
||||
.to_string()
|
||||
.into();
|
||||
req.ip_address = Some(ip_address_from_request)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_string_to_enums(query: String) -> UserResult<GetMultipleMetaDataPayload> {
|
||||
Ok(GetMultipleMetaDataPayload {
|
||||
results: query
|
||||
.split(',')
|
||||
.map(GetMetaDataRequest::from_str)
|
||||
.collect::<Result<Vec<GetMetaDataRequest>, _>>()
|
||||
.map_err(|_| UserErrors::InvalidMetadataRequest.into())
|
||||
.attach_printable("Error Parsing to DashboardMetadata enums")?,
|
||||
})
|
||||
}
|
||||
@ -259,6 +259,10 @@ pub enum Flow {
|
||||
DecisionManagerRetrieveConfig,
|
||||
/// Change password flow
|
||||
ChangePassword,
|
||||
/// Set Dashboard Metadata flow
|
||||
SetDashboardMetadata,
|
||||
/// Get Multiple Dashboard Metadata flow
|
||||
GetMutltipleDashboardMetadata,
|
||||
/// Payment Connector Verify
|
||||
VerifyPaymentConnector,
|
||||
}
|
||||
|
||||
@ -43,6 +43,7 @@ pub struct MockDb {
|
||||
pub organizations: Arc<Mutex<Vec<store::organization::Organization>>>,
|
||||
pub users: Arc<Mutex<Vec<store::user::User>>>,
|
||||
pub user_roles: Arc<Mutex<Vec<store::user_role::UserRole>>>,
|
||||
pub dashboard_metadata: Arc<Mutex<Vec<store::user::dashboard_metadata::DashboardMetadata>>>,
|
||||
}
|
||||
|
||||
impl MockDb {
|
||||
@ -78,6 +79,7 @@ impl MockDb {
|
||||
organizations: Default::default(),
|
||||
users: Default::default(),
|
||||
user_roles: Default::default(),
|
||||
dashboard_metadata: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX IF EXISTS dashboard_metadata_index;
|
||||
DROP TABLE IF EXISTS dashboard_metadata;
|
||||
@ -0,0 +1,15 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE IF NOT EXISTS dashboard_metadata (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(64),
|
||||
merchant_id VARCHAR(64) NOT NULL,
|
||||
org_id VARCHAR(64) NOT NULL,
|
||||
data_key VARCHAR(64) NOT NULL,
|
||||
data_value JSON NOT NULL,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||
last_modified_by VARCHAR(64) NOT NULL,
|
||||
last_modified_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS dashboard_metadata_index ON dashboard_metadata (COALESCE(user_id,'0'), merchant_id, org_id, data_key);
|
||||
Reference in New Issue
Block a user