Skip to content

Commit

Permalink
feat(users): Add preferred_merchant_id column and update user detai…
Browse files Browse the repository at this point in the history
…ls API (#3373)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
  • Loading branch information
ThisIsMani and hyperswitch-bot[bot] authored Jan 18, 2024
1 parent 975986d commit 862a1b5
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 3 deletions.
6 changes: 4 additions & 2 deletions crates/api_models/src/events/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -54,7 +55,8 @@ common_utils::impl_misc_api_event_type!(
InviteUserRequest,
InviteUserResponse,
VerifyEmailRequest,
SendVerifyEmailRequest
SendVerifyEmailRequest,
UpdateUserAccountDetailsRequest
);

#[cfg(feature = "dummy_connector")]
Expand Down
6 changes: 6 additions & 0 deletions crates/api_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
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 @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,8 @@ diesel::table! {
is_verified -> Bool,
created_at -> Timestamp,
last_modified_at -> Timestamp,
#[max_length = 64]
preferred_merchant_id -> Nullable<Varchar>,
}
}

Expand Down
7 changes: 7 additions & 0 deletions crates/diesel_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)]
Expand All @@ -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)]
Expand All @@ -51,6 +54,7 @@ pub enum UserUpdate {
name: Option<String>,
password: Option<Secret<String>>,
is_verified: Option<bool>,
preferred_merchant_id: Option<String>,
},
}

Expand All @@ -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,
},
}
}
Expand Down
46 changes: 46 additions & 0 deletions crates/router/src/core/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ pub async fn change_password(
name: None,
password: Some(new_password_hash),
is_verified: None,
preferred_merchant_id: None,
},
)
.await
Expand Down Expand Up @@ -330,6 +331,7 @@ pub async fn reset_password(
name: None,
password: Some(hash_password),
is_verified: Some(true),
preferred_merchant_id: None,
},
)
.await
Expand Down Expand Up @@ -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)
}
14 changes: 14 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions crates/router/src/db/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
},
};
Expand Down
43 changes: 43 additions & 0 deletions crates/router/src/db/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/routes/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 2 additions & 1 deletion crates/router/src/routes/lock_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions crates/router/src/routes/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 2 additions & 0 deletions crates/router_env/src/logger/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ pub enum Flow {
VerifyEmail,
/// Send verify email
VerifyEmailRequest,
/// Update user account details
UpdateUserAccountDetails,
}

///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE users DROP COLUMN preferred_merchant_id;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE users ADD COLUMN preferred_merchant_id VARCHAR(64);

0 comments on commit 862a1b5

Please sign in to comment.