Skip to content

Commit

Permalink
feat(user): implement invitations api (#5769)
Browse files Browse the repository at this point in the history
  • Loading branch information
racnan committed Sep 2, 2024
1 parent 258212d commit 730c2ba
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 146 deletions.
9 changes: 9 additions & 0 deletions crates/api_models/src/user_role.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use common_enums::PermissionGroup;
use common_utils::pii;
use masking::Secret;

pub mod role;

Expand Down Expand Up @@ -138,3 +139,11 @@ pub struct ListUsersInEntityResponse {
pub email: pii::Email,
pub roles: Vec<role::MinimalRoleInfo>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ListInvitationForUserResponse {
pub entity_id: String,
pub entity_type: common_enums::EntityType,
pub entity_name: Option<Secret<String>>,
pub role_id: String,
}
17 changes: 16 additions & 1 deletion crates/diesel_models/src/query/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ use error_stack::{report, ResultExt};
use router_env::logger;

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

Expand Down Expand Up @@ -201,14 +205,17 @@ impl UserRole {
.await
}

#[allow(clippy::too_many_arguments)]
pub async fn generic_user_roles_list_for_user(
conn: &PgPooledConn,
user_id: String,
org_id: Option<id_type::OrganizationId>,
merchant_id: Option<id_type::MerchantId>,
profile_id: Option<id_type::ProfileId>,
entity_id: Option<String>,
status: Option<UserStatus>,
version: Option<UserRoleVersion>,
limit: Option<u32>,
) -> StorageResult<Vec<Self>> {
let mut query = <Self as HasTable>::table()
.filter(dsl::user_id.eq(user_id))
Expand All @@ -234,6 +241,14 @@ impl UserRole {
query = query.filter(dsl::version.eq(version));
}

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

if let Some(limit) = limit {
query = query.limit(limit.into());
}

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

match generics::db_metrics::track_database_call::<Self, _, _>(
Expand Down
2 changes: 1 addition & 1 deletion crates/diesel_models/src/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub struct UserRole {
pub version: enums::UserRoleVersion,
}

fn get_entity_id_and_type(user_role: &UserRole) -> (Option<String>, Option<EntityType>) {
pub fn get_entity_id_and_type(user_role: &UserRole) -> (Option<String>, Option<EntityType>) {
match (user_role.version, user_role.role_id.as_str()) {
(enums::UserRoleVersion::V1, consts::ROLE_ID_ORGANIZATION_ADMIN) => (
user_role
Expand Down
179 changes: 94 additions & 85 deletions crates/router/src/core/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ use crate::services::email::types as email_types;
use crate::{
consts,
core::encryption::send_request_to_key_service_for_user,
db::domain::user_authentication_method::DEFAULT_USER_AUTH_METHOD,
db::{
domain::user_authentication_method::DEFAULT_USER_AUTH_METHOD,
user_role::ListUserRolesByUserIdPayload,
},
routes::{app::ReqState, SessionState},
services::{authentication as auth, authorization::roles, openidconnect, ApplicationResponse},
types::{domain, transformers::ForeignInto},
Expand Down Expand Up @@ -2282,22 +2285,20 @@ pub async fn list_orgs_for_user(
) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
let orgs = state
.store
.list_user_roles_by_user_id(
user_from_token.user_id.as_str(),
None,
None,
None,
None,
None,
)
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
org_id: None,
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| {
(user_role.status == UserStatus::Active)
.then_some(user_role.org_id)
.flatten()
})
.filter_map(|user_role| user_role.org_id)
.collect::<HashSet<_>>();

let resp = futures::future::try_join_all(
Expand All @@ -2311,7 +2312,11 @@ pub async fn list_orgs_for_user(
org_id: org.get_organization_id(),
org_name: org.get_organization_name(),
})
.collect();
.collect::<Vec<_>>();

if resp.is_empty() {
Err(UserErrors::InternalServerError).attach_printable("No orgs found for a user")?;
}

Ok(ApplicationResponse::Json(resp))
}
Expand Down Expand Up @@ -2344,26 +2349,24 @@ pub async fn list_merchants_for_user_in_org(
merchant_id: merchant_account.get_id().to_owned(),
},
)
.collect()
.collect::<Vec<_>>()
} else {
let merchant_ids = state
.store
.list_user_roles_by_user_id(
user_from_token.user_id.as_str(),
Some(&user_from_token.org_id),
None,
None,
None,
None,
)
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
org_id: Some(&user_from_token.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| {
(user_role.status == UserStatus::Active)
.then_some(user_role.merchant_id)
.flatten()
})
.filter_map(|user_role| user_role.merchant_id)
.collect::<HashSet<_>>()
.into_iter()
.collect();
Expand All @@ -2379,9 +2382,13 @@ pub async fn list_merchants_for_user_in_org(
merchant_id: merchant_account.get_id().to_owned(),
},
)
.collect()
.collect::<Vec<_>>()
};

if merchant_accounts.is_empty() {
Err(UserErrors::InternalServerError).attach_printable("No merchant found for a user")?;
}

Ok(ApplicationResponse::Json(merchant_accounts))
}

Expand Down Expand Up @@ -2427,26 +2434,24 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
profile_name: profile.profile_name,
},
)
.collect()
.collect::<Vec<_>>()
} else {
let profile_ids = state
.store
.list_user_roles_by_user_id(
user_from_token.user_id.as_str(),
Some(&user_from_token.org_id),
Some(&user_from_token.merchant_id),
None,
None,
None,
)
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
org_id: Some(&user_from_token.org_id),
merchant_id: Some(&user_from_token.merchant_id),
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| {
(user_role.status == UserStatus::Active)
.then_some(user_role.profile_id)
.flatten()
})
.filter_map(|user_role| user_role.profile_id)
.collect::<HashSet<_>>();

futures::future::try_join_all(profile_ids.iter().map(|profile_id| {
Expand All @@ -2465,9 +2470,13 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
profile_name: profile.profile_name,
},
)
.collect()
.collect::<Vec<_>>()
};

if profiles.is_empty() {
Err(UserErrors::InternalServerError).attach_printable("No profile found for a user")?;
}

Ok(ApplicationResponse::Json(profiles))
}

Expand Down Expand Up @@ -2503,23 +2512,23 @@ pub async fn switch_org_for_user(

let user_role = state
.store
.list_user_roles_by_user_id(
&user_from_token.user_id,
Some(&request.org_id),
None,
None,
None,
None,
)
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
org_id: Some(&request.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: Some(1),
})
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list user roles by user_id and org_id")?
.into_iter()
.find(|role| role.status == UserStatus::Active)
.pop()
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
"No user role found for the requested org_id".to_string(),
))?
.to_owned();
))?;

let merchant_id = utils::user_role::get_single_merchant_id(&state, &user_role).await?;

Expand Down Expand Up @@ -2547,7 +2556,7 @@ pub async fn switch_org_for_user(
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list business profiles by merchant_id")?
.first()
.pop()
.ok_or(UserErrors::InternalServerError)
.attach_printable("No business profile found for the merchant_id")?
.get_id()
Expand Down Expand Up @@ -2635,7 +2644,7 @@ pub async fn switch_merchant_for_user_in_org(
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list business profiles by merchant_id")?
.first()
.pop()
.ok_or(UserErrors::InternalServerError)
.attach_printable("No business profile found for the given merchant_id")?
.get_id()
Expand Down Expand Up @@ -2688,12 +2697,11 @@ pub async fn switch_merchant_for_user_in_org(
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list business profiles by merchant_id")?
.first()
.pop()
.ok_or(UserErrors::InternalServerError)
.attach_printable("No business profile found for the merchant_id")?
.get_id()
.to_owned();

(
user_from_token.org_id.clone(),
merchant_id,
Expand All @@ -2705,25 +2713,25 @@ pub async fn switch_merchant_for_user_in_org(
EntityType::Merchant | EntityType::Profile => {
let user_role = state
.store
.list_user_roles_by_user_id(
&user_from_token.user_id,
Some(&user_from_token.org_id),
Some(&request.merchant_id),
None,
None,
None,
)
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
org_id: Some(&user_from_token.org_id),
merchant_id: Some(&request.merchant_id),
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: Some(1),
})
.await
.change_context(UserErrors::InternalServerError)
.attach_printable(
"Failed to list user roles for the given user_id, org_id and merchant_id",
)?
.into_iter()
.find(|role| role.status == UserStatus::Active)
.pop()
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
"No user role associated with the requested merchant_id".to_string(),
))?
.to_owned();
))?;

let profile_id = if let Some(profile_id) = &user_role.profile_id {
profile_id.clone()
Expand All @@ -2749,7 +2757,7 @@ pub async fn switch_merchant_for_user_in_org(
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list business profiles for the given merchant_id")?
.first()
.pop()
.ok_or(UserErrors::InternalServerError)
.attach_printable("No business profile found for the given merchant_id")?
.get_id()
Expand Down Expand Up @@ -2846,23 +2854,24 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
EntityType::Profile => {
let user_role = state
.store
.list_user_roles_by_user_id(
&user_from_token.user_id,
Some(&user_from_token.org_id),
Some(&user_from_token.merchant_id),
Some(&request.profile_id),
None,
None,
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload{
user_id:&user_from_token.user_id,
org_id: Some(&user_from_token.org_id),
merchant_id: Some(&user_from_token.merchant_id),
profile_id:Some(&request.profile_id),
entity_id: None,
version:None,
status: Some(UserStatus::Active),
limit: Some(1)
}
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list user roles for the given user_id, org_id, merchant_id and profile_id")?
.into_iter()
.find(|role| role.status == UserStatus::Active)
.pop()
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
"No user role associated with the profile".to_string(),
))?
.to_owned();
))?;

(request.profile_id, user_role.role_id)
}
Expand Down
Loading

0 comments on commit 730c2ba

Please sign in to comment.