Skip to content

Commit

Permalink
feat(users): Add transfer org ownership API (#3603)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
ThisIsMani and hyperswitch-bot[bot] authored Feb 9, 2024
1 parent cfa10aa commit b9c29e7
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 6 deletions.
5 changes: 3 additions & 2 deletions crates/api_models/src/events/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};

use crate::user_role::{
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, GetRoleRequest,
ListRolesResponse, RoleInfoResponse, UpdateUserRoleRequest,
ListRolesResponse, RoleInfoResponse, TransferOrgOwnershipRequest, UpdateUserRoleRequest,
};

common_utils::impl_misc_api_event_type!(
Expand All @@ -12,5 +12,6 @@ common_utils::impl_misc_api_event_type!(
AuthorizationInfoResponse,
UpdateUserRoleRequest,
AcceptInvitationRequest,
DeleteUserRoleRequest
DeleteUserRoleRequest,
TransferOrgOwnershipRequest
);
5 changes: 5 additions & 0 deletions crates/api_models/src/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ pub type AcceptInvitationResponse = DashboardEntryResponse;
pub struct DeleteUserRoleRequest {
pub email: pii::Email,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct TransferOrgOwnershipRequest {
pub email: pii::Email,
}
14 changes: 14 additions & 0 deletions crates/diesel_models/src/query/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ impl UserRole {
.await
}

pub async fn update_by_user_id_org_id(
conn: &PgPooledConn,
user_id: String,
org_id: String,
update: UserRoleUpdate,
) -> StorageResult<Vec<Self>> {
generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
conn,
dsl::user_id.eq(user_id).and(dsl::org_id.eq(org_id)),
UserRoleUpdateInternal::from(update),
)
.await
}

pub async fn delete_by_user_id_merchant_id(
conn: &PgPooledConn,
user_id: String,
Expand Down
52 changes: 51 additions & 1 deletion crates/router/src/core/user_role.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use api_models::user_role as user_role_api;
use api_models::{user as user_api, user_role as user_role_api};
use diesel_models::{enums::UserStatus, user_role::UserRoleUpdate};
use error_stack::ResultExt;
use masking::ExposeInterface;
use router_env::logger;

use crate::{
consts,
core::errors::{StorageErrorExt, UserErrors, UserResponse},
routes::AppState,
services::{
Expand Down Expand Up @@ -135,6 +136,55 @@ pub async fn update_user_role(
Ok(ApplicationResponse::StatusOk)
}

pub async fn transfer_org_ownership(
state: AppState,
user_from_token: auth::UserFromToken,
req: user_role_api::TransferOrgOwnershipRequest,
) -> UserResponse<user_api::DashboardEntryResponse> {
if user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN {
return Err(UserErrors::InvalidRoleOperation.into()).attach_printable(format!(
"role_id = {} is not org_admin",
user_from_token.role_id
));
}

let user_to_be_updated =
utils::user::get_user_from_db_by_email(&state, domain::UserEmail::try_from(req.email)?)
.await
.to_not_found_response(UserErrors::InvalidRoleOperation)
.attach_printable("User not found in our records".to_string())?;

if user_from_token.user_id == user_to_be_updated.get_user_id() {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("User transferring ownership to themselves".to_string());
}

state
.store
.transfer_org_ownership_between_users(
&user_from_token.user_id,
user_to_be_updated.get_user_id(),
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;

auth::blacklist::insert_user_in_blacklist(&state, user_to_be_updated.get_user_id()).await?;
auth::blacklist::insert_user_in_blacklist(&state, &user_from_token.user_id).await?;

let user_from_db = domain::UserFromStorage::from(user_from_token.get_user(&state).await?);
let user_role = user_from_db
.get_role_from_db_by_merchant_id(&state, &user_from_token.merchant_id)
.await
.to_not_found_response(UserErrors::InvalidRoleOperation)?;

let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;

Ok(ApplicationResponse::Json(
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
))
}

pub async fn accept_invitation(
state: AppState,
user_token: auth::UserWithoutMerchantFromToken,
Expand Down
22 changes: 22 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,17 @@ impl UserRoleInterface for KafkaStore {
.await
}

async fn update_user_roles_by_user_id_org_id(
&self,
user_id: &str,
org_id: &str,
update: user_storage::UserRoleUpdate,
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store
.update_user_roles_by_user_id_org_id(user_id, org_id, update)
.await
}

async fn delete_user_role_by_user_id_merchant_id(
&self,
user_id: &str,
Expand All @@ -1981,6 +1992,17 @@ impl UserRoleInterface for KafkaStore {
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store.list_user_roles_by_user_id(user_id).await
}

async fn transfer_org_ownership_between_users(
&self,
from_user_id: &str,
to_user_id: &str,
org_id: &str,
) -> CustomResult<(), errors::StorageError> {
self.diesel_store
.transfer_org_ownership_between_users(from_user_id, to_user_id, org_id)
.await
}
}

#[async_trait::async_trait]
Expand Down
Loading

0 comments on commit b9c29e7

Please sign in to comment.