feat: add create retrieve and update api endpoints for organization resource (#5361)

This commit is contained in:
Hrithikesh
2024-07-24 12:10:37 +05:30
committed by GitHub
parent 192203d3a9
commit 26b878308f
44 changed files with 739 additions and 180 deletions

View File

@ -105,6 +105,73 @@ fn add_publishable_key_to_decision_service(
);
}
#[cfg(feature = "olap")]
pub async fn create_organization(
state: SessionState,
req: api::OrganizationRequest,
) -> RouterResponse<api::OrganizationResponse> {
let db_organization = ForeignFrom::foreign_from(req);
state
.store
.insert_organization(db_organization)
.await
.to_duplicate_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error when creating organization")
.map(ForeignFrom::foreign_from)
.map(service_api::ApplicationResponse::Json)
}
#[cfg(feature = "olap")]
pub async fn update_organization(
state: SessionState,
org_id: api::OrganizationId,
req: api::OrganizationRequest,
) -> RouterResponse<api::OrganizationResponse> {
let organization_update = diesel_models::organization::OrganizationUpdate::Update {
org_name: req.organization_name,
};
state
.store
.update_organization_by_org_id(&org_id.organization_id, organization_update)
.await
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
message: "organization with the given id does not exist".to_string(),
})
.attach_printable(format!(
"Failed to update organization with organization_id: {:?}",
org_id.organization_id
))
.map(ForeignFrom::foreign_from)
.map(service_api::ApplicationResponse::Json)
}
#[cfg(feature = "olap")]
pub async fn get_organization(
state: SessionState,
org_id: api::OrganizationId,
) -> RouterResponse<api::OrganizationResponse> {
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_account_v2"),
feature = "olap"
))]
{
CreateOrValidateOrganization::new(Some(org_id.organization_id))
.create_or_validate(state.store.as_ref())
.await
.map(ForeignFrom::foreign_from)
.map(service_api::ApplicationResponse::Json)
}
#[cfg(all(feature = "v2", feature = "merchant_account_v2", feature = "olap"))]
{
CreateOrValidateOrganization::new(org_id.organization_id)
.create_or_validate(state.store.as_ref())
.await
.map(ForeignFrom::foreign_from)
.map(service_api::ApplicationResponse::Json)
}
}
#[cfg(feature = "olap")]
pub async fn create_merchant_account(
state: SessionState,
@ -257,7 +324,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate {
)
.await?;
let organization_id = CreateOrValidateOrganization::new(self.organization_id)
let organization = CreateOrValidateOrganization::new(self.organization_id)
.create_or_validate(db)
.await?;
@ -316,7 +383,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate {
payout_routing_algorithm: self.payout_routing_algorithm,
#[cfg(not(feature = "payouts"))]
payout_routing_algorithm: None,
organization_id,
organization_id: organization.org_id,
is_recon_enabled: false,
default_profile: None,
recon_status: diesel_models::enums::ReconStatus::NotRequested,
@ -342,7 +409,9 @@ enum CreateOrValidateOrganization {
#[cfg(any(feature = "v1", feature = "v2"))]
Create,
/// Validates if this organization exists in the records
Validate { organization_id: String },
Validate {
organization_id: id_type::OrganizationId,
},
}
#[cfg(feature = "olap")]
@ -355,7 +424,7 @@ impl CreateOrValidateOrganization {
/// Create an action to either create or validate the given organization_id
/// If organization_id is passed, then validate if this organization exists
/// If not passed, create a new organization
fn new(organization_id: Option<String>) -> Self {
fn new(organization_id: Option<id_type::OrganizationId>) -> Self {
if let Some(organization_id) = organization_id {
Self::Validate { organization_id }
} else {
@ -365,34 +434,33 @@ impl CreateOrValidateOrganization {
#[cfg(all(feature = "v2", feature = "merchant_account_v2", feature = "olap"))]
/// Create an action to validate the provided organization_id
fn new(organization_id: String) -> Self {
fn new(organization_id: id_type::OrganizationId) -> Self {
Self::Validate { organization_id }
}
#[cfg(feature = "olap")]
/// Apply the action, whether to create the organization or validate the given organization_id
async fn create_or_validate(&self, db: &dyn StorageInterface) -> RouterResult<String> {
Ok(match self {
async fn create_or_validate(
&self,
db: &dyn StorageInterface,
) -> RouterResult<diesel_models::organization::Organization> {
match self {
#[cfg(any(feature = "v1", feature = "v2"))]
Self::Create => {
let new_organization = api_models::organization::OrganizationNew::new(None);
let db_organization = ForeignFrom::foreign_from(new_organization);
let organization = db
.insert_organization(db_organization)
db.insert_organization(db_organization)
.await
.to_duplicate_response(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error when creating organization")?;
organization.org_id
.attach_printable("Error when creating organization")
}
Self::Validate { organization_id } => {
db.find_organization_by_org_id(organization_id)
.await
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
message: "organization with the given id does not exist".to_string(),
})?;
organization_id.to_string()
}
})
Self::Validate { organization_id } => db
.find_organization_by_org_id(organization_id)
.await
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
message: "organization with the given id does not exist".to_string(),
}),
}
}
}

View File

@ -36,6 +36,7 @@ pub mod user;
pub mod user_authentication_method;
pub mod user_key_store;
pub mod user_role;
use common_utils::id_type;
use diesel_models::{
fraud_check::{FraudCheck, FraudCheckUpdate},
organization::{Organization, OrganizationNew, OrganizationUpdate},
@ -346,14 +347,14 @@ impl OrganizationInterface for KafkaStore {
}
async fn find_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<Organization, StorageError> {
self.diesel_store.find_organization_by_org_id(org_id).await
}
async fn update_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
update: OrganizationUpdate,
) -> CustomResult<Organization, StorageError> {
self.diesel_store

View File

@ -1,3 +1,4 @@
use common_utils::id_type;
use diesel_models::{enums, user::dashboard_metadata as storage};
use error_stack::{report, ResultExt};
use router_env::{instrument, tracing};
@ -20,7 +21,7 @@ pub trait DashboardMetadataInterface {
&self,
user_id: Option<String>,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
data_key: enums::DashboardMetadata,
dashboard_metadata_update: storage::DashboardMetadataUpdate,
) -> CustomResult<storage::DashboardMetadata, errors::StorageError>;
@ -29,14 +30,14 @@ pub trait DashboardMetadataInterface {
&self,
user_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
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,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError>;
@ -73,7 +74,7 @@ impl DashboardMetadataInterface for Store {
&self,
user_id: Option<String>,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
data_key: enums::DashboardMetadata,
dashboard_metadata_update: storage::DashboardMetadataUpdate,
) -> CustomResult<storage::DashboardMetadata, errors::StorageError> {
@ -95,7 +96,7 @@ impl DashboardMetadataInterface for Store {
&self,
user_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
@ -114,7 +115,7 @@ impl DashboardMetadataInterface for Store {
async fn find_merchant_scoped_dashboard_metadata(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
@ -202,7 +203,7 @@ impl DashboardMetadataInterface for MockDb {
&self,
user_id: Option<String>,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
data_key: enums::DashboardMetadata,
dashboard_metadata_update: storage::DashboardMetadataUpdate,
) -> CustomResult<storage::DashboardMetadata, errors::StorageError> {
@ -237,7 +238,7 @@ impl DashboardMetadataInterface for MockDb {
&self,
user_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
let dashboard_metadata = self.dashboard_metadata.lock().await;
@ -250,7 +251,7 @@ impl DashboardMetadataInterface for MockDb {
.map(|user_id_inner| user_id_inner == user_id)
.unwrap_or(false)
&& metadata_inner.merchant_id == merchant_id
&& metadata_inner.org_id == org_id
&& metadata_inner.org_id == *org_id
&& data_keys.contains(&metadata_inner.data_key)
})
.cloned()
@ -259,7 +260,7 @@ impl DashboardMetadataInterface for MockDb {
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:?}",
merchant_id = {merchant_id}, org_id = {org_id:?} and data_keys = {data_keys:?}",
))
.into());
}
@ -269,7 +270,7 @@ impl DashboardMetadataInterface for MockDb {
async fn find_merchant_scoped_dashboard_metadata(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
let dashboard_metadata = self.dashboard_metadata.lock().await;
@ -277,7 +278,7 @@ impl DashboardMetadataInterface for MockDb {
.iter()
.filter(|metadata_inner| {
metadata_inner.merchant_id == merchant_id
&& metadata_inner.org_id == org_id
&& metadata_inner.org_id == *org_id
&& data_keys.contains(&metadata_inner.data_key)
})
.cloned()
@ -286,7 +287,7 @@ impl DashboardMetadataInterface for MockDb {
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:?}",
org_id = {org_id:?} and data_keyss = {data_keys:?}",
))
.into());
}

View File

@ -2563,7 +2563,7 @@ impl UserRoleInterface for KafkaStore {
async fn update_user_roles_by_user_id_org_id(
&self,
user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
update: user_storage::UserRoleUpdate,
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store
@ -2592,7 +2592,7 @@ impl UserRoleInterface for KafkaStore {
&self,
from_user_id: &str,
to_user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<(), errors::StorageError> {
self.diesel_store
.transfer_org_ownership_between_users(from_user_id, to_user_id, org_id)
@ -2622,7 +2622,7 @@ impl DashboardMetadataInterface for KafkaStore {
&self,
user_id: Option<String>,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
data_key: enums::DashboardMetadata,
dashboard_metadata_update: storage::DashboardMetadataUpdate,
) -> CustomResult<storage::DashboardMetadata, errors::StorageError> {
@ -2641,7 +2641,7 @@ impl DashboardMetadataInterface for KafkaStore {
&self,
user_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
self.diesel_store
@ -2652,7 +2652,7 @@ impl DashboardMetadataInterface for KafkaStore {
async fn find_merchant_scoped_dashboard_metadata(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
data_keys: Vec<enums::DashboardMetadata>,
) -> CustomResult<Vec<storage::DashboardMetadata>, errors::StorageError> {
self.diesel_store
@ -2949,7 +2949,7 @@ impl RoleInterface for KafkaStore {
&self,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Role, errors::StorageError> {
self.diesel_store
.find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id)
@ -2976,7 +2976,7 @@ impl RoleInterface for KafkaStore {
async fn list_all_roles(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
self.diesel_store.list_all_roles(merchant_id, org_id).await
}

View File

@ -1,4 +1,4 @@
use common_utils::errors::CustomResult;
use common_utils::{errors::CustomResult, id_type};
use diesel_models::organization as storage;
use error_stack::report;
use router_env::{instrument, tracing};
@ -14,12 +14,12 @@ pub trait OrganizationInterface {
async fn find_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Organization, errors::StorageError>;
async fn update_organization_by_org_id(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
update: storage::OrganizationUpdate,
) -> CustomResult<storage::Organization, errors::StorageError>;
}
@ -41,10 +41,10 @@ impl OrganizationInterface for Store {
#[instrument(skip_all)]
async fn find_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Organization, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Organization::find_by_org_id(&conn, org_id.to_string())
storage::Organization::find_by_org_id(&conn, org_id.to_owned())
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
@ -52,12 +52,12 @@ impl OrganizationInterface for Store {
#[instrument(skip_all)]
async fn update_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
update: storage::OrganizationUpdate,
) -> CustomResult<storage::Organization, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
storage::Organization::update_by_org_id(&conn, org_id.to_string(), update)
storage::Organization::update_by_org_id(&conn, org_id.to_owned(), update)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
@ -90,17 +90,18 @@ impl OrganizationInterface for super::MockDb {
async fn find_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Organization, errors::StorageError> {
let organizations = self.organizations.lock().await;
organizations
.iter()
.find(|org| org.org_id == org_id)
.find(|org| org.org_id == *org_id)
.cloned()
.ok_or(
errors::StorageError::ValueNotFound(format!(
"No organization available for org_id = {org_id}"
"No organization available for org_id = {:?}",
org_id
))
.into(),
)
@ -108,14 +109,14 @@ impl OrganizationInterface for super::MockDb {
async fn update_organization_by_org_id(
&self,
org_id: &str,
org_id: &id_type::OrganizationId,
update: storage::OrganizationUpdate,
) -> CustomResult<storage::Organization, errors::StorageError> {
let mut organizations = self.organizations.lock().await;
organizations
.iter_mut()
.find(|org| org.org_id == org_id)
.find(|org| org.org_id == *org_id)
.map(|org| match &update {
storage::OrganizationUpdate::Update { org_name } => storage::Organization {
org_name: org_name.clone(),
@ -124,7 +125,8 @@ impl OrganizationInterface for super::MockDb {
})
.ok_or(
errors::StorageError::ValueNotFound(format!(
"No organization available for org_id = {org_id}"
"No organization available for org_id = {:?}",
org_id
))
.into(),
)

View File

@ -1,4 +1,5 @@
use common_enums::enums;
use common_utils::id_type;
use diesel_models::role as storage;
use error_stack::{report, ResultExt};
use router_env::{instrument, tracing};
@ -26,7 +27,7 @@ pub trait RoleInterface {
&self,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Role, errors::StorageError>;
async fn update_role_by_role_id(
@ -43,7 +44,7 @@ pub trait RoleInterface {
async fn list_all_roles(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
}
@ -76,7 +77,7 @@ impl RoleInterface for Store {
&self,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Role, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
storage::Role::find_by_role_id_in_merchant_scope(&conn, role_id, merchant_id, org_id)
@ -111,7 +112,7 @@ impl RoleInterface for Store {
async fn list_all_roles(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
storage::Role::list_roles(&conn, merchant_id, org_id)
@ -174,7 +175,7 @@ impl RoleInterface for MockDb {
&self,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<storage::Role, errors::StorageError> {
let roles = self.roles.lock().await;
roles
@ -182,13 +183,13 @@ impl RoleInterface for MockDb {
.find(|role| {
role.role_id == role_id
&& (role.merchant_id == merchant_id
|| (role.org_id == org_id && role.scope == enums::RoleScope::Organization))
|| (role.org_id == *org_id && role.scope == enums::RoleScope::Organization))
})
.cloned()
.ok_or(
errors::StorageError::ValueNotFound(format!(
"No role available in merchant scope for role_id = {role_id}, \
merchant_id = {merchant_id} and org_id = {org_id}"
merchant_id = {merchant_id} and org_id = {org_id:?}"
))
.into(),
)
@ -246,7 +247,7 @@ impl RoleInterface for MockDb {
async fn list_all_roles(
&self,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let roles = self.roles.lock().await;
@ -254,7 +255,7 @@ impl RoleInterface for MockDb {
.iter()
.filter(|role| {
role.merchant_id == merchant_id
|| (role.org_id == org_id
|| (role.org_id == *org_id
&& role.scope == diesel_models::enums::RoleScope::Organization)
})
.cloned()
@ -262,7 +263,7 @@ impl RoleInterface for MockDb {
if roles_list.is_empty() {
return Err(errors::StorageError::ValueNotFound(format!(
"No role found for merchant id = {} and org_id = {}",
"No role found for merchant id = {} and org_id = {:?}",
merchant_id, org_id
))
.into());

View File

@ -1,6 +1,7 @@
use std::{collections::HashSet, ops::Not};
use async_bb8_diesel::AsyncConnection;
use common_utils::id_type;
use diesel_models::{enums, user_role as storage};
use error_stack::{report, ResultExt};
use router_env::{instrument, tracing};
@ -40,7 +41,7 @@ pub trait UserRoleInterface {
async fn update_user_roles_by_user_id_org_id(
&self,
user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
update: storage::UserRoleUpdate,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
@ -64,7 +65,7 @@ pub trait UserRoleInterface {
&self,
from_user_id: &str,
to_user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<(), errors::StorageError>;
}
@ -131,7 +132,7 @@ impl UserRoleInterface for Store {
async fn update_user_roles_by_user_id_org_id(
&self,
user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
update: storage::UserRoleUpdate,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
@ -189,7 +190,7 @@ impl UserRoleInterface for Store {
&self,
from_user_id: &str,
to_user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<(), errors::StorageError> {
let conn = connection::pg_connection_write(self)
.await
@ -237,7 +238,7 @@ impl UserRoleInterface for Store {
user_id: to_user_id.to_string(),
merchant_id: old_role.merchant_id,
role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
org_id: org_id.to_string(),
org_id: org_id.to_owned(),
status: enums::UserStatus::Active,
created_by: from_user_id.to_string(),
last_modified_by: from_user_id.to_string(),
@ -374,13 +375,13 @@ impl UserRoleInterface for MockDb {
async fn update_user_roles_by_user_id_org_id(
&self,
user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
update: storage::UserRoleUpdate,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
let mut user_roles = self.user_roles.lock().await;
let mut updated_user_roles = Vec::new();
for user_role in user_roles.iter_mut() {
if user_role.user_id == user_id && user_role.org_id == org_id {
if user_role.user_id == user_id && user_role.org_id == *org_id {
match &update {
storage::UserRoleUpdate::UpdateRole {
role_id,
@ -402,7 +403,7 @@ impl UserRoleInterface for MockDb {
}
if updated_user_roles.is_empty() {
Err(errors::StorageError::ValueNotFound(format!(
"No user role available for user_id = {user_id} and org_id = {org_id}"
"No user role available for user_id = {user_id} and org_id = {org_id:?}"
))
.into())
} else {
@ -414,7 +415,7 @@ impl UserRoleInterface for MockDb {
&self,
from_user_id: &str,
to_user_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<(), errors::StorageError> {
let old_org_admin_user_roles = self
.update_user_roles_by_user_id_org_id(
@ -453,7 +454,7 @@ impl UserRoleInterface for MockDb {
user_id: to_user_id.to_string(),
merchant_id: old_roles.merchant_id,
role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
org_id: org_id.to_string(),
org_id: org_id.to_owned(),
status: enums::UserStatus::Active,
created_by: from_user_id.to_string(),
last_modified_by: from_user_id.to_string(),

View File

@ -146,6 +146,7 @@ pub fn mk_app(
#[cfg(feature = "olap")]
{
server_app = server_app
.service(routes::Organization::server(state.clone()))
.service(routes::MerchantAccount::server(state.clone()))
.service(routes::ApiKeys::server(state.clone()))
.service(routes::Files::server(state.clone()))

View File

@ -64,7 +64,7 @@ pub use self::app::{
Refunds, SessionState, User, Webhooks,
};
#[cfg(feature = "olap")]
pub use self::app::{Blocklist, Routing, Verify, WebhookEvents};
pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents};
#[cfg(feature = "payouts")]
pub use self::app::{PayoutLink, Payouts};
#[cfg(all(

View File

@ -1,4 +1,6 @@
use actix_web::{web, HttpRequest, HttpResponse};
#[cfg(feature = "olap")]
use common_utils::id_type;
use router_env::{instrument, tracing, Flow};
use super::app::AppState;
@ -8,6 +10,71 @@ use crate::{
types::api::admin,
};
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationCreate))]
pub async fn organization_create(
state: web::Data<AppState>,
req: HttpRequest,
json_payload: web::Json<admin::OrganizationRequest>,
) -> HttpResponse {
let flow = Flow::OrganizationCreate;
Box::pin(api::server_wrap(
flow,
state,
&req,
json_payload.into_inner(),
|state, _, req, _| create_organization(state, req),
&auth::AdminApiAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationUpdate))]
pub async fn organization_update(
state: web::Data<AppState>,
req: HttpRequest,
org_id: web::Path<id_type::OrganizationId>,
json_payload: web::Json<admin::OrganizationRequest>,
) -> HttpResponse {
let flow = Flow::OrganizationUpdate;
let organization_id = org_id.into_inner();
let org_id = admin::OrganizationId { organization_id };
Box::pin(api::server_wrap(
flow,
state,
&req,
json_payload.into_inner(),
|state, _, req, _| update_organization(state, org_id.clone(), req),
&auth::AdminApiAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::OrganizationRetrieve))]
pub async fn organization_retrieve(
state: web::Data<AppState>,
req: HttpRequest,
org_id: web::Path<id_type::OrganizationId>,
) -> HttpResponse {
let flow = Flow::OrganizationRetrieve;
let organization_id = org_id.into_inner();
let payload = admin::OrganizationId { organization_id };
Box::pin(api::server_wrap(
flow,
state,
&req,
payload,
|state, _, req, _| get_organization(state, req),
&auth::AdminApiAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantsAccountCreate))]
pub async fn merchant_account_create(
@ -51,11 +118,9 @@ pub async fn retrieve_merchant_account(
) -> HttpResponse {
let flow = Flow::MerchantsAccountRetrieve;
let merchant_id = mid.into_inner();
let payload = web::Json(admin::MerchantId {
merchant_id: merchant_id.to_owned(),
})
.into_inner();
let payload = admin::MerchantId {
merchant_id: merchant_id.clone(),
};
api::server_wrap(
flow,
state,

View File

@ -1028,6 +1028,22 @@ impl Blocklist {
}
}
#[cfg(feature = "olap")]
pub struct Organization;
#[cfg(feature = "olap")]
impl Organization {
pub fn server(state: AppState) -> Scope {
web::scope("/organization")
.app_data(web::Data::new(state))
.service(web::resource("").route(web::post().to(organization_create)))
.service(
web::resource("/{id}")
.route(web::get().to(organization_retrieve))
.route(web::put().to(organization_update)),
)
}
}
pub struct MerchantAccount;
#[cfg(all(feature = "v2", feature = "olap", feature = "merchant_account_v2"))]

View File

@ -6,6 +6,7 @@ pub enum ApiIdentifier {
Payments,
Refunds,
Webhooks,
Organization,
MerchantAccount,
MerchantConnector,
Configs,
@ -48,6 +49,10 @@ impl From<Flow> for ApiIdentifier {
| Flow::MerchantTransferKey
| Flow::MerchantAccountList => Self::MerchantAccount,
Flow::OrganizationCreate | Flow::OrganizationRetrieve | Flow::OrganizationUpdate => {
Self::Organization
}
Flow::RoutingCreateConfig
| Flow::RoutingLinkConfig
| Flow::RoutingUnlinkConfig

View File

@ -7,7 +7,7 @@ use api_models::{
};
use async_trait::async_trait;
use common_enums::TokenPurpose;
use common_utils::date_time;
use common_utils::{date_time, id_type};
use error_stack::{report, ResultExt};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use masking::PeekInterface;
@ -169,7 +169,7 @@ pub struct AuthToken {
pub merchant_id: String,
pub role_id: String,
pub exp: u64,
pub org_id: String,
pub org_id: id_type::OrganizationId,
}
#[cfg(feature = "olap")]
@ -179,7 +179,7 @@ impl AuthToken {
merchant_id: String,
role_id: String,
settings: &Settings,
org_id: String,
org_id: id_type::OrganizationId,
) -> UserResult<String> {
let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS);
let exp = jwt::generate_exp(exp_duration)?.as_secs();
@ -199,7 +199,7 @@ pub struct UserFromToken {
pub user_id: String,
pub merchant_id: String,
pub role_id: String,
pub org_id: String,
pub org_id: id_type::OrganizationId,
}
pub struct UserIdFromAuth {

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use common_enums::PermissionGroup;
use common_utils::id_type;
use error_stack::ResultExt;
use redis_interface::RedisConnectionPool;
use router_env::logger;
@ -79,7 +80,7 @@ async fn get_permissions_from_db<A>(
state: &A,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> RouterResult<Vec<permissions::Permission>>
where
A: SessionStateInfo + Sync,

View File

@ -1,7 +1,7 @@
use std::collections::HashSet;
use common_enums::{PermissionGroup, RoleScope};
use common_utils::errors::CustomResult;
use common_utils::{errors::CustomResult, id_type};
use super::{permission_groups::get_permissions_vec, permissions::Permission};
use crate::{core::errors, routes::SessionState};
@ -70,7 +70,7 @@ impl RoleInfo {
state: &SessionState,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> CustomResult<Self, errors::StorageError> {
if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
Ok(role.clone())

View File

@ -1,12 +1,15 @@
use std::collections::HashMap;
pub use api_models::admin::{
BusinessProfileCreate, BusinessProfileResponse, BusinessProfileUpdate, MerchantAccountCreate,
MerchantAccountDeleteResponse, MerchantAccountResponse, MerchantAccountUpdate,
MerchantConnectorCreate, MerchantConnectorDeleteResponse, MerchantConnectorDetails,
MerchantConnectorDetailsWrap, MerchantConnectorId, MerchantConnectorResponse, MerchantDetails,
MerchantId, PaymentMethodsEnabled, ToggleAllKVRequest, ToggleAllKVResponse, ToggleKVRequest,
ToggleKVResponse, WebhookDetails,
pub use api_models::{
admin::{
BusinessProfileCreate, BusinessProfileResponse, BusinessProfileUpdate,
MerchantAccountCreate, MerchantAccountDeleteResponse, MerchantAccountResponse,
MerchantAccountUpdate, MerchantConnectorCreate, MerchantConnectorDeleteResponse,
MerchantConnectorDetails, MerchantConnectorDetailsWrap, MerchantConnectorId,
MerchantConnectorResponse, MerchantDetails, MerchantId, PaymentMethodsEnabled,
ToggleAllKVRequest, ToggleAllKVResponse, ToggleKVRequest, ToggleKVResponse, WebhookDetails,
},
organization::{OrganizationId, OrganizationRequest, OrganizationResponse},
};
use common_utils::{
ext_traits::{AsyncExt, Encode, ValueExt},
@ -21,9 +24,18 @@ use masking::{ExposeInterface, PeekInterface, Secret};
use crate::{
core::{errors, payment_methods::cards::create_encrypted_data},
routes::SessionState,
types::{domain, storage, transformers::ForeignTryFrom},
types::{domain, storage, transformers::ForeignTryFrom, ForeignFrom},
};
impl ForeignFrom<diesel_models::organization::Organization> for OrganizationResponse {
fn foreign_from(org: diesel_models::organization::Organization) -> Self {
Self {
organization_id: org.org_id,
organization_name: org.org_name,
}
}
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_account_v2")

View File

@ -4,10 +4,8 @@ use api_models::{
admin as admin_api, organization as api_org, user as user_api, user_role as user_role_api,
};
use common_enums::TokenPurpose;
#[cfg(any(feature = "v1", feature = "v2"))]
use common_utils::id_type;
use common_utils::{
crypto::Encryptable, errors::CustomResult, new_type::MerchantName, pii,
crypto::Encryptable, errors::CustomResult, id_type, new_type::MerchantName, pii,
types::keymanager::Identifier,
};
use diesel_models::{
@ -256,7 +254,7 @@ impl NewUserOrganization {
.attach_printable("Error while inserting organization")
}
pub fn get_organization_id(&self) -> String {
pub fn get_organization_id(&self) -> id_type::OrganizationId {
self.0.org_id.clone()
}
}
@ -288,8 +286,10 @@ impl From<user_api::ConnectAccountRequest> for NewUserOrganization {
}
}
impl From<(user_api::CreateInternalUserRequest, String)> for NewUserOrganization {
fn from((_value, org_id): (user_api::CreateInternalUserRequest, String)) -> Self {
impl From<(user_api::CreateInternalUserRequest, id_type::OrganizationId)> for NewUserOrganization {
fn from(
(_value, org_id): (user_api::CreateInternalUserRequest, id_type::OrganizationId),
) -> Self {
let new_organization = api_org::OrganizationNew {
org_id,
org_name: None,
@ -511,10 +511,12 @@ impl TryFrom<user_api::SignUpWithMerchantIdRequest> for NewUserMerchant {
}
}
impl TryFrom<(user_api::CreateInternalUserRequest, String)> for NewUserMerchant {
impl TryFrom<(user_api::CreateInternalUserRequest, id_type::OrganizationId)> for NewUserMerchant {
type Error = error_stack::Report<UserErrors>;
fn try_from(value: (user_api::CreateInternalUserRequest, String)) -> UserResult<Self> {
fn try_from(
value: (user_api::CreateInternalUserRequest, id_type::OrganizationId),
) -> UserResult<Self> {
let merchant_id =
MerchantId::new(consts::user_role::INTERNAL_USER_MERCHANT_ID.to_string())?;
let new_organization = NewUserOrganization::from(value);
@ -756,11 +758,11 @@ impl TryFrom<user_api::ConnectAccountRequest> for NewUser {
}
}
impl TryFrom<(user_api::CreateInternalUserRequest, String)> for NewUser {
impl TryFrom<(user_api::CreateInternalUserRequest, id_type::OrganizationId)> for NewUser {
type Error = error_stack::Report<UserErrors>;
fn try_from(
(value, org_id): (user_api::CreateInternalUserRequest, String),
(value, org_id): (user_api::CreateInternalUserRequest, id_type::OrganizationId),
) -> UserResult<Self> {
let user_id = uuid::Uuid::new_v4().to_string();
let email = value.email.clone().try_into()?;

View File

@ -1285,6 +1285,18 @@ impl ForeignFrom<api_models::organization::OrganizationNew>
}
}
impl ForeignFrom<api_models::organization::OrganizationRequest>
for diesel_models::organization::OrganizationNew
{
fn foreign_from(item: api_models::organization::OrganizationRequest) -> Self {
let org_new = api_models::organization::OrganizationNew::new(None);
Self {
org_id: org_new.org_id,
org_name: item.organization_name,
}
}
}
impl ForeignFrom<gsm_api_types::GsmCreateRequest> for storage::GatewayStatusMappingNew {
fn foreign_from(value: gsm_api_types::GsmCreateRequest) -> Self {
Self {

View File

@ -2,7 +2,9 @@ use std::{collections::HashMap, sync::Arc};
use api_models::user as user_api;
use common_enums::UserAuthType;
use common_utils::{encryption::Encryption, errors::CustomResult, types::keymanager::Identifier};
use common_utils::{
encryption::Encryption, errors::CustomResult, id_type, types::keymanager::Identifier,
};
use diesel_models::{enums::UserStatus, user_role::UserRole};
use error_stack::ResultExt;
use masking::{ExposeInterface, Secret};
@ -99,7 +101,7 @@ pub async fn generate_jwt_auth_token_with_custom_role_attributes(
state: &SessionState,
user: &UserFromStorage,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
role_id: String,
) -> UserResult<Secret<String>> {
let token = AuthToken::new_token(

View File

@ -4,6 +4,7 @@ use actix_web::http::header::HeaderMap;
use api_models::user::dashboard_metadata::{
GetMetaDataRequest, GetMultipleMetaDataPayload, ProdIntent, SetMetaDataRequest,
};
use common_utils::id_type;
use diesel_models::{
enums::DashboardMetadata as DBEnum,
user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew, DashboardMetadataUpdate},
@ -20,7 +21,7 @@ pub async fn insert_merchant_scoped_metadata_to_db(
state: &SessionState,
user_id: String,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {
@ -53,7 +54,7 @@ pub async fn insert_user_scoped_metadata_to_db(
state: &SessionState,
user_id: String,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {
@ -86,7 +87,7 @@ pub async fn insert_user_scoped_metadata_to_db(
pub async fn get_merchant_scoped_metadata_from_db(
state: &SessionState,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
metadata_keys: Vec<DBEnum>,
) -> UserResult<Vec<DashboardMetadata>> {
state
@ -100,7 +101,7 @@ pub async fn get_user_scoped_metadata_from_db(
state: &SessionState,
user_id: String,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
metadata_keys: Vec<DBEnum>,
) -> UserResult<Vec<DashboardMetadata>> {
match state
@ -124,7 +125,7 @@ pub async fn update_merchant_scoped_metadata(
state: &SessionState,
user_id: String,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {
@ -152,7 +153,7 @@ pub async fn update_user_scoped_metadata(
state: &SessionState,
user_id: String,
merchant_id: String,
org_id: String,
org_id: id_type::OrganizationId,
metadata_key: DBEnum,
metadata_value: impl serde::Serialize,
) -> UserResult<DashboardMetadata> {

View File

@ -2,6 +2,7 @@ use std::collections::HashSet;
use api_models::user_role as user_role_api;
use common_enums::PermissionGroup;
use common_utils::id_type;
use diesel_models::user_role::UserRole;
use error_stack::{report, ResultExt};
use router_env::logger;
@ -77,7 +78,7 @@ pub async fn validate_role_name(
state: &SessionState,
role_name: &domain::RoleName,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> UserResult<()> {
let role_name_str = role_name.clone().get_role_name();
@ -109,7 +110,7 @@ pub async fn set_role_permissions_in_cache_by_user_role(
state,
user_role.role_id.as_str(),
user_role.merchant_id.as_str(),
user_role.org_id.as_str(),
&user_role.org_id,
)
.await
.map_err(|e| logger::error!("Error setting permissions in cache {:?}", e))
@ -120,7 +121,7 @@ pub async fn set_role_permissions_in_cache_if_required(
state: &SessionState,
role_id: &str,
merchant_id: &str,
org_id: &str,
org_id: &id_type::OrganizationId,
) -> UserResult<()> {
if roles::predefined_roles::PREDEFINED_ROLES.contains_key(role_id) {
return Ok(());