Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(user): add list org, merchant and profile api #5662

Merged
merged 9 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/api_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,21 @@ pub struct UserKeyTransferRequest {
pub struct UserTransferKeyResponse {
pub total_transferred: usize,
}

#[derive(Debug, serde::Serialize)]
pub struct ListOrgsForUserResponse {
pub org_id: id_type::OrganizationId,
pub org_name: Option<String>,
}

#[derive(Debug, serde::Serialize)]
pub struct ListMerchantsForUserInOrgResponse {
pub merchant_id: id_type::MerchantId,
pub merchant_name: OptionalEncryptableName,
}

#[derive(Debug, serde::Serialize)]
pub struct ListProfilesForUserInOrgAndMerchantAccountResponse {
pub profile_id: String,
pub profile_name: String,
}
62 changes: 59 additions & 3 deletions crates/diesel_models/src/query/user_role.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use async_bb8_diesel::AsyncRunQueryDsl;
use common_utils::id_type;
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
use diesel::{
associations::HasTable, debug_query, pg::Pg, result::Error as DieselError,
BoolExpressionMethods, ExpressionMethods, QueryDsl,
};
use error_stack::{report, ResultExt};

use crate::{
enums::UserRoleVersion, query::generics, schema::user_roles::dsl, user_role::*, PgPooledConn,
StorageResult,
enums::UserRoleVersion, errors, query::generics, schema::user_roles::dsl, user_role::*,
PgPooledConn, StorageResult,
};

impl UserRoleNew {
Expand Down Expand Up @@ -179,4 +184,55 @@ impl UserRole {
generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(conn, predicate)
.await
}

pub async fn generic_user_roles_list(
conn: &PgPooledConn,
user_id: String,
org_id: Option<id_type::OrganizationId>,
merchant_id: Option<id_type::MerchantId>,
profile_id: Option<String>,
entity_id: Option<String>,
version: Option<UserRoleVersion>,
) -> StorageResult<Vec<Self>> {
let mut query = <Self as HasTable>::table()
.filter(dsl::user_id.eq(user_id))
.into_boxed();

if let Some(org_id) = org_id {
query = query.filter(dsl::org_id.eq(org_id));
}

if let Some(merchant_id) = merchant_id {
query = query.filter(dsl::merchant_id.eq(merchant_id));
}

if let Some(profile_id) = profile_id {
query = query.filter(dsl::profile_id.eq(profile_id));
}

if let Some(entity_id) = entity_id {
query = query.filter(dsl::entity_id.eq(entity_id));
}

if let Some(version) = version {
query = query.filter(dsl::version.eq(version));
}

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

match generics::db_metrics::track_database_call::<Self, _, _>(
query.get_results_async(conn),
generics::db_metrics::DatabaseOperation::Filter,
)
.await
{
Ok(value) => Ok(value),
Err(err) => match err {
DieselError::NotFound => {
Err(report!(err)).change_context(errors::DatabaseError::NotFound)
}
_ => Err(report!(err)).change_context(errors::DatabaseError::Others),
},
}
}
}
199 changes: 198 additions & 1 deletion crates/router/src/core/user.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};

use api_models::{
payments::RedirectionResponse,
user::{self as user_api, InviteMultipleUserResponse},
};
use common_enums::EntityType;
use common_utils::{type_name, types::keymanager::Identifier};
#[cfg(feature = "email")]
use diesel_models::user_role::UserRoleUpdate;
use diesel_models::{
enums::{TotpStatus, UserRoleVersion, UserStatus},
organization::OrganizationBridge,
user as storage_user,
user_authentication_method::{UserAuthenticationMethodNew, UserAuthenticationMethodUpdate},
user_role::UserRoleNew,
Expand Down Expand Up @@ -2563,3 +2565,198 @@ pub async fn terminate_auth_select(
token,
)
}

pub async fn list_orgs_for_user(
state: SessionState,
user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
let orgs = state
.store
.list_user_roles(
user_from_token.user_id.as_str(),
None,
None,
None,
None,
None,
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| {
(user_role.status == UserStatus::Active)
.then_some(user_role.org_id)
.flatten()
})
.collect::<HashSet<_>>();

let resp = futures::future::try_join_all(
orgs.iter()
.map(|org_id| state.store.find_organization_by_org_id(org_id)),
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(|org| user_api::ListOrgsForUserResponse {
org_id: org.get_organization_id(),
org_name: org.get_organization_name(),
})
.collect();

Ok(ApplicationResponse::Json(resp))
}

pub async fn list_merchants_for_user_in_org(
state: SessionState,
user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::ListMerchantsForUserInOrgResponse>> {
let role_info = roles::RoleInfo::from_role_id(
&state,
&user_from_token.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
let merchant_accounts = if role_info.get_entity_type() == EntityType::Organization {
state
.store
.list_merchant_accounts_by_organization_id(
&(&state).into(),
user_from_token.org_id.get_string_repr(),
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(
|merchant_account| user_api::ListMerchantsForUserInOrgResponse {
merchant_name: merchant_account.merchant_name.clone(),
merchant_id: merchant_account.get_id().to_owned(),
},
)
.collect()
} else {
let merchant_ids = state
.store
.list_user_roles(
user_from_token.user_id.as_str(),
Some(&user_from_token.org_id),
None,
None,
None,
None,
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| {
(user_role.status == UserStatus::Active)
.then_some(user_role.merchant_id)
.flatten()
})
.collect::<HashSet<_>>()
.into_iter()
.collect();
state
.store
.list_multiple_merchant_accounts(&(&state).into(), merchant_ids)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(
|merchant_account| user_api::ListMerchantsForUserInOrgResponse {
merchant_name: merchant_account.merchant_name.clone(),
merchant_id: merchant_account.get_id().to_owned(),
},
)
.collect()
};

Ok(ApplicationResponse::Json(merchant_accounts))
}

pub async fn list_profiles_for_user_in_org_and_merchant_account(
state: SessionState,
user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::ListProfilesForUserInOrgAndMerchantAccountResponse>> {
let role_info = roles::RoleInfo::from_role_id(
&state,
&user_from_token.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;

let key_manager_state = &(&state).into();
let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&user_from_token.merchant_id,
&state.store.get_master_key().to_vec().into(),
)
.await
.change_context(UserErrors::InternalServerError)?;
let user_role_level = role_info.get_entity_type();
let profiles =
if user_role_level == EntityType::Organization || user_role_level == EntityType::Merchant {
state
.store
.list_business_profile_by_merchant_id(
key_manager_state,
&key_store,
&user_from_token.merchant_id,
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(
|profile| user_api::ListProfilesForUserInOrgAndMerchantAccountResponse {
profile_id: profile.profile_id,
profile_name: profile.profile_name,
},
)
.collect()
} else {
let profile_ids = state
.store
.list_user_roles(
user_from_token.user_id.as_str(),
Some(&user_from_token.org_id),
Some(&user_from_token.merchant_id),
None,
None,
None,
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| {
(user_role.status == UserStatus::Active)
.then_some(user_role.profile_id)
.flatten()
})
.collect::<HashSet<_>>();

futures::future::try_join_all(profile_ids.iter().map(|profile_id| {
state.store.find_business_profile_by_profile_id(
key_manager_state,
&key_store,
profile_id,
)
}))
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(
|profile| user_api::ListProfilesForUserInOrgAndMerchantAccountResponse {
profile_id: profile.profile_id,
profile_name: profile.profile_name,
},
)
.collect()
};

Ok(ApplicationResponse::Json(profiles))
}
14 changes: 14 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2871,6 +2871,20 @@ impl UserRoleInterface for KafkaStore {
.list_user_roles_by_merchant_id(merchant_id, version)
.await
}

async fn list_user_roles(
&self,
user_id: &str,
org_id: Option<&id_type::OrganizationId>,
merchant_id: Option<&id_type::MerchantId>,
profile_id: Option<&String>,
entity_id: Option<&String>,
version: Option<enums::UserRoleVersion>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
self.diesel_store
.list_user_roles(user_id, org_id, merchant_id, profile_id, entity_id, version)
.await
}
}

#[async_trait::async_trait]
Expand Down
Loading
Loading