Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(users): Add preferred_merchant_id column and update user details API #3373

Merged
merged 5 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Loading