mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-31 01:57:45 +08:00
feat(users): Add preferred_merchant_id column and update user details API (#3373)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -13,7 +13,8 @@ use crate::user::{
|
||||
AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
|
||||
DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
|
||||
InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignUpRequest,
|
||||
SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UserMerchantCreate, VerifyEmailRequest,
|
||||
SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UpdateUserAccountDetailsRequest,
|
||||
UserMerchantCreate, VerifyEmailRequest,
|
||||
};
|
||||
|
||||
impl ApiEventMetric for DashboardEntryResponse {
|
||||
@ -54,7 +55,8 @@ common_utils::impl_misc_api_event_type!(
|
||||
InviteUserRequest,
|
||||
InviteUserResponse,
|
||||
VerifyEmailRequest,
|
||||
SendVerifyEmailRequest
|
||||
SendVerifyEmailRequest,
|
||||
UpdateUserAccountDetailsRequest
|
||||
);
|
||||
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
|
||||
@ -147,3 +147,9 @@ pub struct VerifyTokenResponse {
|
||||
pub merchant_id: String,
|
||||
pub user_email: pii::Email,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct UpdateUserAccountDetailsRequest {
|
||||
pub name: Option<Secret<String>>,
|
||||
pub preferred_merchant_id: Option<String>,
|
||||
}
|
||||
|
||||
@ -19,6 +19,20 @@ impl UserRole {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_user_id_merchant_id(
|
||||
conn: &PgPooledConn,
|
||||
user_id: String,
|
||||
merchant_id: String,
|
||||
) -> StorageResult<Self> {
|
||||
generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
|
||||
conn,
|
||||
dsl::user_id
|
||||
.eq(user_id)
|
||||
.and(dsl::merchant_id.eq(merchant_id)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_by_user_id_merchant_id(
|
||||
conn: &PgPooledConn,
|
||||
user_id: String,
|
||||
|
||||
@ -1056,6 +1056,8 @@ diesel::table! {
|
||||
is_verified -> Bool,
|
||||
created_at -> Timestamp,
|
||||
last_modified_at -> Timestamp,
|
||||
#[max_length = 64]
|
||||
preferred_merchant_id -> Nullable<Varchar>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ pub struct User {
|
||||
pub is_verified: bool,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
pub preferred_merchant_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@ -33,6 +34,7 @@ pub struct UserNew {
|
||||
pub is_verified: bool,
|
||||
pub created_at: Option<PrimitiveDateTime>,
|
||||
pub last_modified_at: Option<PrimitiveDateTime>,
|
||||
pub preferred_merchant_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
|
||||
@ -42,6 +44,7 @@ pub struct UserUpdateInternal {
|
||||
password: Option<Secret<String>>,
|
||||
is_verified: Option<bool>,
|
||||
last_modified_at: PrimitiveDateTime,
|
||||
preferred_merchant_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -51,6 +54,7 @@ pub enum UserUpdate {
|
||||
name: Option<String>,
|
||||
password: Option<Secret<String>>,
|
||||
is_verified: Option<bool>,
|
||||
preferred_merchant_id: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -63,16 +67,19 @@ impl From<UserUpdate> for UserUpdateInternal {
|
||||
password: None,
|
||||
is_verified: Some(true),
|
||||
last_modified_at,
|
||||
preferred_merchant_id: None,
|
||||
},
|
||||
UserUpdate::AccountUpdate {
|
||||
name,
|
||||
password,
|
||||
is_verified,
|
||||
preferred_merchant_id,
|
||||
} => Self {
|
||||
name,
|
||||
password,
|
||||
is_verified,
|
||||
last_modified_at,
|
||||
preferred_merchant_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,6 +253,7 @@ pub async fn change_password(
|
||||
name: None,
|
||||
password: Some(new_password_hash),
|
||||
is_verified: None,
|
||||
preferred_merchant_id: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@ -330,6 +331,7 @@ pub async fn reset_password(
|
||||
name: None,
|
||||
password: Some(hash_password),
|
||||
is_verified: Some(true),
|
||||
preferred_merchant_id: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
@ -786,3 +788,47 @@ pub async fn verify_token(
|
||||
user_email: user.email,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn update_user_details(
|
||||
state: AppState,
|
||||
user_token: auth::UserFromToken,
|
||||
req: user_api::UpdateUserAccountDetailsRequest,
|
||||
) -> UserResponse<()> {
|
||||
let user: domain::UserFromStorage = state
|
||||
.store
|
||||
.find_user_by_id(&user_token.user_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.into();
|
||||
|
||||
let name = req.name.map(domain::UserName::new).transpose()?;
|
||||
|
||||
if let Some(ref preferred_merchant_id) = req.preferred_merchant_id {
|
||||
let _ = state
|
||||
.store
|
||||
.find_user_role_by_user_id_merchant_id(user.get_user_id(), preferred_merchant_id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.current_context().is_db_not_found() {
|
||||
e.change_context(UserErrors::MerchantIdNotFound)
|
||||
} else {
|
||||
e.change_context(UserErrors::InternalServerError)
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
let user_update = storage_user::UserUpdate::AccountUpdate {
|
||||
name: name.map(|x| x.get_secret().expose()),
|
||||
password: None,
|
||||
is_verified: None,
|
||||
preferred_merchant_id: req.preferred_merchant_id,
|
||||
};
|
||||
|
||||
state
|
||||
.store
|
||||
.update_user_by_user_id(user.get_user_id(), user_update)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
@ -1927,12 +1927,24 @@ impl UserRoleInterface for KafkaStore {
|
||||
) -> CustomResult<user_storage::UserRole, errors::StorageError> {
|
||||
self.diesel_store.insert_user_role(user_role).await
|
||||
}
|
||||
|
||||
async fn find_user_role_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> CustomResult<user_storage::UserRole, errors::StorageError> {
|
||||
self.diesel_store.find_user_role_by_user_id(user_id).await
|
||||
}
|
||||
|
||||
async fn find_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
) -> CustomResult<user_storage::UserRole, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.find_user_role_by_user_id_merchant_id(user_id, merchant_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@ -1943,9 +1955,11 @@ impl UserRoleInterface for KafkaStore {
|
||||
.update_user_role_by_user_id_merchant_id(user_id, merchant_id, update)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_user_role(&self, user_id: &str) -> CustomResult<bool, errors::StorageError> {
|
||||
self.diesel_store.delete_user_role(user_id).await
|
||||
}
|
||||
|
||||
async fn list_user_roles_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
|
||||
@ -145,6 +145,7 @@ impl UserInterface for MockDb {
|
||||
is_verified: user_data.is_verified,
|
||||
created_at: user_data.created_at.unwrap_or(time_now),
|
||||
last_modified_at: user_data.created_at.unwrap_or(time_now),
|
||||
preferred_merchant_id: user_data.preferred_merchant_id,
|
||||
};
|
||||
users.push(user.clone());
|
||||
Ok(user)
|
||||
@ -207,10 +208,14 @@ impl UserInterface for MockDb {
|
||||
name,
|
||||
password,
|
||||
is_verified,
|
||||
preferred_merchant_id,
|
||||
} => storage::User {
|
||||
name: name.clone().map(Secret::new).unwrap_or(user.name.clone()),
|
||||
password: password.clone().unwrap_or(user.password.clone()),
|
||||
is_verified: is_verified.unwrap_or(user.is_verified),
|
||||
preferred_merchant_id: preferred_merchant_id
|
||||
.clone()
|
||||
.or(user.preferred_merchant_id.clone()),
|
||||
..user.to_owned()
|
||||
},
|
||||
};
|
||||
|
||||
@ -14,16 +14,25 @@ pub trait UserRoleInterface {
|
||||
&self,
|
||||
user_role: storage::UserRoleNew,
|
||||
) -> CustomResult<storage::UserRole, errors::StorageError>;
|
||||
|
||||
async fn find_user_role_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> CustomResult<storage::UserRole, errors::StorageError>;
|
||||
|
||||
async fn find_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
) -> CustomResult<storage::UserRole, errors::StorageError>;
|
||||
|
||||
async fn update_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
update: storage::UserRoleUpdate,
|
||||
) -> CustomResult<storage::UserRole, errors::StorageError>;
|
||||
|
||||
async fn delete_user_role(&self, user_id: &str) -> CustomResult<bool, errors::StorageError>;
|
||||
|
||||
async fn list_user_roles_by_user_id(
|
||||
@ -57,6 +66,22 @@ impl UserRoleInterface for Store {
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn find_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
) -> CustomResult<storage::UserRole, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::UserRole::find_by_user_id_merchant_id(
|
||||
&conn,
|
||||
user_id.to_owned(),
|
||||
merchant_id.to_owned(),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn update_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@ -148,6 +173,24 @@ impl UserRoleInterface for MockDb {
|
||||
)
|
||||
}
|
||||
|
||||
async fn find_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
merchant_id: &str,
|
||||
) -> CustomResult<storage::UserRole, errors::StorageError> {
|
||||
let user_roles = self.user_roles.lock().await;
|
||||
user_roles
|
||||
.iter()
|
||||
.find(|user_role| user_role.user_id == user_id && user_role.merchant_id == merchant_id)
|
||||
.cloned()
|
||||
.ok_or(
|
||||
errors::StorageError::ValueNotFound(format!(
|
||||
"No user role available for user_id = {user_id} and merchant_id = {merchant_id}"
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn update_user_role_by_user_id_merchant_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
|
||||
@ -921,6 +921,7 @@ impl User {
|
||||
.service(web::resource("/role/list").route(web::get().to(list_roles)))
|
||||
.service(web::resource("/role/{role_id}").route(web::get().to(get_role)))
|
||||
.service(web::resource("/user/invite").route(web::post().to(invite_user)))
|
||||
.service(web::resource("/update").route(web::post().to(update_user_account_details)))
|
||||
.service(
|
||||
web::resource("/data")
|
||||
.route(web::get().to(get_multiple_dashboard_metadata))
|
||||
|
||||
@ -178,7 +178,8 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::InviteUser
|
||||
| Flow::UserSignUpWithMerchantId
|
||||
| Flow::VerifyEmail
|
||||
| Flow::VerifyEmailRequest => Self::User,
|
||||
| Flow::VerifyEmailRequest
|
||||
| Flow::UpdateUserAccountDetails => Self::User,
|
||||
|
||||
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {
|
||||
Self::UserRole
|
||||
|
||||
@ -403,3 +403,21 @@ pub async fn verify_recon_token(state: web::Data<AppState>, http_req: HttpReques
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_user_account_details(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<user_api::UpdateUserAccountDetailsRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::UpdateUserAccountDetails;
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
user_core::update_user_details,
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@ -331,6 +331,8 @@ pub enum Flow {
|
||||
VerifyEmail,
|
||||
/// Send verify email
|
||||
VerifyEmailRequest,
|
||||
/// Update user account details
|
||||
UpdateUserAccountDetails,
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE users DROP COLUMN preferred_merchant_id;
|
||||
@ -0,0 +1,2 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE users ADD COLUMN preferred_merchant_id VARCHAR(64);
|
||||
Reference in New Issue
Block a user