Skip to content

Commit

Permalink
feat: add routes and endpoints for roles
Browse files Browse the repository at this point in the history
  • Loading branch information
ThisIsMani committed Nov 30, 2023
1 parent 195b1cc commit d695586
Show file tree
Hide file tree
Showing 14 changed files with 442 additions and 260 deletions.
1 change: 1 addition & 0 deletions crates/api_models/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod payouts;
pub mod refund;
pub mod routing;
pub mod user;
pub mod user_role;

use common_utils::{
events::{ApiEventMetric, ApiEventsType},
Expand Down
12 changes: 10 additions & 2 deletions crates/api_models/src/events/user.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use common_utils::events::{ApiEventMetric, ApiEventsType};

use crate::user::{ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse};
use crate::user::{
ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse,
CreateInternalUserRequest, SwitchMerchantIdRequest, UserMerchantCreate,
};

impl ApiEventMetric for ConnectAccountResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Expand All @@ -13,4 +16,9 @@ impl ApiEventMetric for ConnectAccountResponse {

impl ApiEventMetric for ConnectAccountRequest {}

common_utils::impl_misc_api_event_type!(ChangePasswordRequest);
common_utils::impl_misc_api_event_type!(
ChangePasswordRequest,
SwitchMerchantIdRequest,
CreateInternalUserRequest,
UserMerchantCreate
);
15 changes: 15 additions & 0 deletions crates/api_models/src/events/user_role.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use common_utils::events::{ApiEventMetric, ApiEventsType};

use crate::user_role::{
AuthorizationInfoResponse, GetRoleRequest, GetUsersResponse, ListRolesResponse,
RoleInfoResponse, UpdateUserRoleRequest,
};

common_utils::impl_misc_api_event_type!(
ListRolesResponse,
RoleInfoResponse,
GetRoleRequest,
AuthorizationInfoResponse,
GetUsersResponse,
UpdateUserRoleRequest
);
17 changes: 17 additions & 0 deletions crates/api_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,20 @@ pub struct ChangePasswordRequest {
pub new_password: Secret<String>,
pub old_password: Secret<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SwitchMerchantIdRequest {
pub merchant_id: String,
}

#[derive(serde::Deserialize, Debug, serde::Serialize)]
pub struct CreateInternalUserRequest {
pub name: Secret<String>,
pub email: pii::Email,
pub password: Secret<String>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct UserMerchantCreate {
pub company_name: String,
}
34 changes: 0 additions & 34 deletions crates/api_models/src/user_role.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,6 @@
use common_utils::pii;
use masking::Secret;

pub struct CreateInternalUserRequest {
pub name: Secret<String>,
pub email: pii::Email,
pub password: Secret<String>,
}

#[derive(serde::Deserialize, Debug, serde::Serialize)]
pub struct UserListRequest {
#[serde(skip_deserializing)]
pub onboarding_steps: String,
pub subscribed: bool,
pub start_date: String,
pub last_date: Option<String>,
pub last_modified: Option<String>,
}

#[derive(serde::Serialize, Clone, Debug)]
pub struct UserListResponse {
email: pii::Email,
user_id: String,
onboarding_modified_at: time::PrimitiveDateTime,
onboarding_step: i32,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SwitchMerchantIdRequest {
pub merchant_id: String,
}

#[derive(Debug, serde::Serialize)]
pub struct ListRolesResponse(pub Vec<RoleInfoResponse>);

Expand Down Expand Up @@ -149,8 +120,3 @@ pub struct UpdateUserRoleRequest {
pub user_id: String,
pub role_id: String,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct UserMerchantCreate {
pub company_name: String,
}
232 changes: 207 additions & 25 deletions crates/router/src/core/user.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use api_models::user as api;
use diesel_models::enums::UserStatus;
use api_models::user as user_api;
use diesel_models::{enums::UserStatus, user as storage_user};
use error_stack::{IntoReport, ResultExt};
use masking::{ExposeInterface, Secret};
use router_env::env;
Expand All @@ -9,14 +9,15 @@ use crate::{
consts,
db::user::UserInterface,
routes::AppState,
services::{authentication::UserFromToken, ApplicationResponse},
services::{authentication as auth, ApplicationResponse},
types::domain,
utils,
};

pub async fn connect_account(
state: AppState,
request: api::ConnectAccountRequest,
) -> UserResponse<api::ConnectAccountResponse> {
request: user_api::ConnectAccountRequest,
) -> UserResponse<user_api::ConnectAccountResponse> {
let find_user = state
.store
.find_user_by_email(request.email.clone().expose().expose().as_str())
Expand All @@ -32,15 +33,17 @@ pub async fn connect_account(
.get_jwt_auth_token(state.clone(), user_role.org_id)
.await?;

return Ok(ApplicationResponse::Json(api::ConnectAccountResponse {
token: Secret::new(jwt_token),
merchant_id: user_role.merchant_id,
name: user_from_db.get_name(),
email: user_from_db.get_email(),
verification_days_left: None,
user_role: user_role.role_id,
user_id: user_from_db.get_user_id().to_string(),
}));
return Ok(ApplicationResponse::Json(
user_api::ConnectAccountResponse {
token: Secret::new(jwt_token),
merchant_id: user_role.merchant_id,
name: user_from_db.get_name(),
email: user_from_db.get_email(),
verification_days_left: None,
user_role: user_role.role_id,
user_id: user_from_db.get_user_id().to_string(),
},
));
} else if find_user
.map_err(|e| e.current_context().is_db_not_found())
.err()
Expand Down Expand Up @@ -92,24 +95,26 @@ pub async fn connect_account(
logger::info!(?send_email_result);
}

return Ok(ApplicationResponse::Json(api::ConnectAccountResponse {
token: Secret::new(jwt_token),
merchant_id: user_role.merchant_id,
name: user_from_db.get_name(),
email: user_from_db.get_email(),
verification_days_left: None,
user_role: user_role.role_id,
user_id: user_from_db.get_user_id().to_string(),
}));
return Ok(ApplicationResponse::Json(
user_api::ConnectAccountResponse {
token: Secret::new(jwt_token),
merchant_id: user_role.merchant_id,
name: user_from_db.get_name(),
email: user_from_db.get_email(),
verification_days_left: None,
user_role: user_role.role_id,
user_id: user_from_db.get_user_id().to_string(),
},
));
} else {
Err(UserErrors::InternalServerError.into())
}
}

pub async fn change_password(
state: AppState,
request: api::ChangePasswordRequest,
user_from_token: UserFromToken,
request: user_api::ChangePasswordRequest,
user_from_token: auth::UserFromToken,
) -> UserResponse<()> {
let user: domain::UserFromStorage =
UserInterface::find_user_by_id(&*state.store, &user_from_token.user_id)
Expand Down Expand Up @@ -137,3 +142,180 @@ pub async fn change_password(

Ok(ApplicationResponse::StatusOk)
}

pub async fn create_internal_user(
state: AppState,
request: user_api::CreateInternalUserRequest,
) -> UserResponse<()> {
let new_user = domain::NewUser::try_from(request)?;

let mut store_user: storage_user::UserNew = new_user.clone().try_into()?;
store_user.set_is_verified(true);

let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
consts::user_role::INTERNAL_USER_MERCHANT_ID,
&state.store.get_master_key().to_vec().into(),
)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::MerchantIdNotFound)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;

state
.store
.find_merchant_account_by_merchant_id(
consts::user_role::INTERNAL_USER_MERCHANT_ID,
&key_store,
)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::MerchantIdNotFound)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;

state
.store
.insert_user(store_user)
.await
.map_err(|e| {
if e.current_context().is_db_unique_violation() {
e.change_context(UserErrors::UserExists)
} else {
e.change_context(UserErrors::InternalServerError)
}
})
.map(domain::user::UserFromStorage::from)?;

new_user
.insert_user_role_in_db(
state,
consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
UserStatus::Active,
)
.await?;

Ok(ApplicationResponse::StatusOk)
}

pub async fn switch_merchant_id(
state: AppState,
request: user_api::SwitchMerchantIdRequest,
user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::ConnectAccountResponse> {
if !utils::user_role::is_internal_role(&user_from_token.role_id) {
let merchant_list =
utils::user_role::get_merchant_ids_for_user(state.clone(), &user_from_token.user_id)
.await?;
if !merchant_list.contains(&request.merchant_id) {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("User doesn't have access to switch");
}
}

if user_from_token.merchant_id == request.merchant_id {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("User switch to same merchant id.");
}

let user = state
.store
.find_user_by_id(&user_from_token.user_id)
.await
.change_context(UserErrors::InternalServerError)?;

let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
request.merchant_id.as_str(),
&state.store.get_master_key().to_vec().into(),
)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::MerchantIdNotFound)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;

let org_id = state
.store
.find_merchant_account_by_merchant_id(request.merchant_id.as_str(), &key_store)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::MerchantIdNotFound)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?
.organization_id;

let user = domain::UserFromStorage::from(user);
let user_role = state
.store
.find_user_role_by_user_id(user.get_user_id())
.await
.change_context(UserErrors::InternalServerError)?;

let token = Box::pin(user.get_jwt_auth_token_with_custom_merchant_id(
state.clone(),
request.merchant_id.clone(),
org_id,
))
.await?
.into();

Ok(ApplicationResponse::Json(
user_api::ConnectAccountResponse {
merchant_id: request.merchant_id,
token,
name: user.get_name(),
email: user.get_email(),
user_id: user.get_user_id().to_string(),
verification_days_left: None,
user_role: user_role.role_id,
},
))
}

pub async fn create_merchant_account(
state: AppState,
user_from_token: auth::UserFromToken,
req: user_api::UserMerchantCreate,
) -> UserResponse<()> {
let user_from_db: domain::UserFromStorage =
user_from_token.get_user(state.clone()).await?.into();

let new_user = domain::NewUser::try_from((user_from_db, req, user_from_token))?;
let new_merchant = new_user.get_new_merchant();
new_merchant
.create_new_merchant_and_insert_in_db(state.to_owned())
.await?;

let role_insertion_res = new_user
.insert_user_role_in_db(
state.clone(),
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
UserStatus::Active,
)
.await;
if let Err(e) = role_insertion_res {
let _ = state
.store
.delete_merchant_account_by_merchant_id(new_merchant.get_merchant_id().as_str())
.await;
return Err(e);
}

Ok(ApplicationResponse::StatusOk)
}
Loading

0 comments on commit d695586

Please sign in to comment.