Skip to content

Commit b9c29e7

Browse files
feat(users): Add transfer org ownership API (#3603)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent cfa10aa commit b9c29e7

File tree

10 files changed

+341
-6
lines changed

10 files changed

+341
-6
lines changed

crates/api_models/src/events/user_role.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
22

33
use crate::user_role::{
44
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, GetRoleRequest,
5-
ListRolesResponse, RoleInfoResponse, UpdateUserRoleRequest,
5+
ListRolesResponse, RoleInfoResponse, TransferOrgOwnershipRequest, UpdateUserRoleRequest,
66
};
77

88
common_utils::impl_misc_api_event_type!(
@@ -12,5 +12,6 @@ common_utils::impl_misc_api_event_type!(
1212
AuthorizationInfoResponse,
1313
UpdateUserRoleRequest,
1414
AcceptInvitationRequest,
15-
DeleteUserRoleRequest
15+
DeleteUserRoleRequest,
16+
TransferOrgOwnershipRequest
1617
);

crates/api_models/src/user_role.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,8 @@ pub type AcceptInvitationResponse = DashboardEntryResponse;
108108
pub struct DeleteUserRoleRequest {
109109
pub email: pii::Email,
110110
}
111+
112+
#[derive(Debug, serde::Deserialize, serde::Serialize)]
113+
pub struct TransferOrgOwnershipRequest {
114+
pub email: pii::Email,
115+
}

crates/diesel_models/src/query/user_role.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ impl UserRole {
5454
.await
5555
}
5656

57+
pub async fn update_by_user_id_org_id(
58+
conn: &PgPooledConn,
59+
user_id: String,
60+
org_id: String,
61+
update: UserRoleUpdate,
62+
) -> StorageResult<Vec<Self>> {
63+
generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
64+
conn,
65+
dsl::user_id.eq(user_id).and(dsl::org_id.eq(org_id)),
66+
UserRoleUpdateInternal::from(update),
67+
)
68+
.await
69+
}
70+
5771
pub async fn delete_by_user_id_merchant_id(
5872
conn: &PgPooledConn,
5973
user_id: String,

crates/router/src/core/user_role.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use api_models::user_role as user_role_api;
1+
use api_models::{user as user_api, user_role as user_role_api};
22
use diesel_models::{enums::UserStatus, user_role::UserRoleUpdate};
33
use error_stack::ResultExt;
44
use masking::ExposeInterface;
55
use router_env::logger;
66

77
use crate::{
8+
consts,
89
core::errors::{StorageErrorExt, UserErrors, UserResponse},
910
routes::AppState,
1011
services::{
@@ -135,6 +136,55 @@ pub async fn update_user_role(
135136
Ok(ApplicationResponse::StatusOk)
136137
}
137138

139+
pub async fn transfer_org_ownership(
140+
state: AppState,
141+
user_from_token: auth::UserFromToken,
142+
req: user_role_api::TransferOrgOwnershipRequest,
143+
) -> UserResponse<user_api::DashboardEntryResponse> {
144+
if user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN {
145+
return Err(UserErrors::InvalidRoleOperation.into()).attach_printable(format!(
146+
"role_id = {} is not org_admin",
147+
user_from_token.role_id
148+
));
149+
}
150+
151+
let user_to_be_updated =
152+
utils::user::get_user_from_db_by_email(&state, domain::UserEmail::try_from(req.email)?)
153+
.await
154+
.to_not_found_response(UserErrors::InvalidRoleOperation)
155+
.attach_printable("User not found in our records".to_string())?;
156+
157+
if user_from_token.user_id == user_to_be_updated.get_user_id() {
158+
return Err(UserErrors::InvalidRoleOperation.into())
159+
.attach_printable("User transferring ownership to themselves".to_string());
160+
}
161+
162+
state
163+
.store
164+
.transfer_org_ownership_between_users(
165+
&user_from_token.user_id,
166+
user_to_be_updated.get_user_id(),
167+
&user_from_token.org_id,
168+
)
169+
.await
170+
.change_context(UserErrors::InternalServerError)?;
171+
172+
auth::blacklist::insert_user_in_blacklist(&state, user_to_be_updated.get_user_id()).await?;
173+
auth::blacklist::insert_user_in_blacklist(&state, &user_from_token.user_id).await?;
174+
175+
let user_from_db = domain::UserFromStorage::from(user_from_token.get_user(&state).await?);
176+
let user_role = user_from_db
177+
.get_role_from_db_by_merchant_id(&state, &user_from_token.merchant_id)
178+
.await
179+
.to_not_found_response(UserErrors::InvalidRoleOperation)?;
180+
181+
let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
182+
183+
Ok(ApplicationResponse::Json(
184+
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
185+
))
186+
}
187+
138188
pub async fn accept_invitation(
139189
state: AppState,
140190
user_token: auth::UserWithoutMerchantFromToken,

crates/router/src/db/kafka_store.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,17 @@ impl UserRoleInterface for KafkaStore {
19651965
.await
19661966
}
19671967

1968+
async fn update_user_roles_by_user_id_org_id(
1969+
&self,
1970+
user_id: &str,
1971+
org_id: &str,
1972+
update: user_storage::UserRoleUpdate,
1973+
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
1974+
self.diesel_store
1975+
.update_user_roles_by_user_id_org_id(user_id, org_id, update)
1976+
.await
1977+
}
1978+
19681979
async fn delete_user_role_by_user_id_merchant_id(
19691980
&self,
19701981
user_id: &str,
@@ -1981,6 +1992,17 @@ impl UserRoleInterface for KafkaStore {
19811992
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
19821993
self.diesel_store.list_user_roles_by_user_id(user_id).await
19831994
}
1995+
1996+
async fn transfer_org_ownership_between_users(
1997+
&self,
1998+
from_user_id: &str,
1999+
to_user_id: &str,
2000+
org_id: &str,
2001+
) -> CustomResult<(), errors::StorageError> {
2002+
self.diesel_store
2003+
.transfer_org_ownership_between_users(from_user_id, to_user_id, org_id)
2004+
.await
2005+
}
19842006
}
19852007

19862008
#[async_trait::async_trait]

0 commit comments

Comments
 (0)