mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(user): support multiple invites (#3422)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -89,6 +89,16 @@ pub struct InviteUserResponse {
|
|||||||
pub password: Option<Secret<String>>,
|
pub password: Option<Secret<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub struct InviteMultipleUserResponse {
|
||||||
|
pub email: pii::Email,
|
||||||
|
pub is_email_sent: bool,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub password: Option<Secret<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct SwitchMerchantIdRequest {
|
pub struct SwitchMerchantIdRequest {
|
||||||
pub merchant_id: String,
|
pub merchant_id: String,
|
||||||
|
|||||||
@ -56,6 +56,8 @@ pub enum UserErrors {
|
|||||||
ChangePasswordError,
|
ChangePasswordError,
|
||||||
#[error("InvalidDeleteOperation")]
|
#[error("InvalidDeleteOperation")]
|
||||||
InvalidDeleteOperation,
|
InvalidDeleteOperation,
|
||||||
|
#[error("MaxInvitationsError")]
|
||||||
|
MaxInvitationsError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||||
@ -64,107 +66,118 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
|||||||
let sub_code = "UR";
|
let sub_code = "UR";
|
||||||
match self {
|
match self {
|
||||||
Self::InternalServerError => {
|
Self::InternalServerError => {
|
||||||
AER::InternalServerError(ApiError::new("HE", 0, "Something Went Wrong", None))
|
AER::InternalServerError(ApiError::new("HE", 0, self.get_error_message(), None))
|
||||||
|
}
|
||||||
|
Self::InvalidCredentials => {
|
||||||
|
AER::Unauthorized(ApiError::new(sub_code, 1, self.get_error_message(), None))
|
||||||
|
}
|
||||||
|
Self::UserNotFound => {
|
||||||
|
AER::Unauthorized(ApiError::new(sub_code, 2, self.get_error_message(), None))
|
||||||
|
}
|
||||||
|
Self::UserExists => {
|
||||||
|
AER::BadRequest(ApiError::new(sub_code, 3, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::InvalidCredentials => AER::Unauthorized(ApiError::new(
|
|
||||||
sub_code,
|
|
||||||
1,
|
|
||||||
"Incorrect email or password",
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Self::UserNotFound => AER::Unauthorized(ApiError::new(
|
|
||||||
sub_code,
|
|
||||||
2,
|
|
||||||
"Email doesn’t exist. Register",
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Self::UserExists => AER::BadRequest(ApiError::new(
|
|
||||||
sub_code,
|
|
||||||
3,
|
|
||||||
"An account already exists with this email",
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Self::LinkInvalid => {
|
Self::LinkInvalid => {
|
||||||
AER::Unauthorized(ApiError::new(sub_code, 4, "Invalid or expired link", None))
|
AER::Unauthorized(ApiError::new(sub_code, 4, self.get_error_message(), None))
|
||||||
|
}
|
||||||
|
Self::UnverifiedUser => {
|
||||||
|
AER::Unauthorized(ApiError::new(sub_code, 5, self.get_error_message(), None))
|
||||||
|
}
|
||||||
|
Self::InvalidOldPassword => {
|
||||||
|
AER::BadRequest(ApiError::new(sub_code, 6, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::UnverifiedUser => AER::Unauthorized(ApiError::new(
|
|
||||||
sub_code,
|
|
||||||
5,
|
|
||||||
"Kindly verify your account",
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Self::InvalidOldPassword => AER::BadRequest(ApiError::new(
|
|
||||||
sub_code,
|
|
||||||
6,
|
|
||||||
"Old password incorrect. Please enter the correct password",
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Self::EmailParsingError => {
|
Self::EmailParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 7, "Invalid Email", None))
|
AER::BadRequest(ApiError::new(sub_code, 7, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::NameParsingError => {
|
Self::NameParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 8, "Invalid Name", None))
|
AER::BadRequest(ApiError::new(sub_code, 8, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::PasswordParsingError => {
|
Self::PasswordParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 9, "Invalid Password", None))
|
AER::BadRequest(ApiError::new(sub_code, 9, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::UserAlreadyVerified => {
|
Self::UserAlreadyVerified => {
|
||||||
AER::Unauthorized(ApiError::new(sub_code, 11, "User already verified", None))
|
AER::Unauthorized(ApiError::new(sub_code, 11, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::CompanyNameParsingError => {
|
Self::CompanyNameParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 14, "Invalid Company Name", None))
|
AER::BadRequest(ApiError::new(sub_code, 14, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::MerchantAccountCreationError(error_message) => {
|
Self::MerchantAccountCreationError(error_message) => {
|
||||||
AER::InternalServerError(ApiError::new(sub_code, 15, error_message, None))
|
AER::InternalServerError(ApiError::new(sub_code, 15, error_message, None))
|
||||||
}
|
}
|
||||||
Self::InvalidEmailError => {
|
Self::InvalidEmailError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 16, "Invalid Email", None))
|
AER::BadRequest(ApiError::new(sub_code, 16, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::MerchantIdNotFound => {
|
Self::MerchantIdNotFound => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 18, "Invalid Merchant ID", None))
|
AER::BadRequest(ApiError::new(sub_code, 18, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::MetadataAlreadySet => {
|
Self::MetadataAlreadySet => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 19, "Metadata already set", None))
|
AER::BadRequest(ApiError::new(sub_code, 19, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::DuplicateOrganizationId => AER::InternalServerError(ApiError::new(
|
Self::DuplicateOrganizationId => AER::InternalServerError(ApiError::new(
|
||||||
sub_code,
|
sub_code,
|
||||||
21,
|
21,
|
||||||
"An Organization with the id already exists",
|
self.get_error_message(),
|
||||||
None,
|
None,
|
||||||
)),
|
)),
|
||||||
Self::InvalidRoleId => {
|
Self::InvalidRoleId => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 22, "Invalid Role ID", None))
|
AER::BadRequest(ApiError::new(sub_code, 22, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::InvalidRoleOperation => AER::BadRequest(ApiError::new(
|
Self::InvalidRoleOperation => {
|
||||||
|
AER::BadRequest(ApiError::new(sub_code, 23, self.get_error_message(), None))
|
||||||
|
}
|
||||||
|
Self::IpAddressParsingFailed => AER::InternalServerError(ApiError::new(
|
||||||
sub_code,
|
sub_code,
|
||||||
23,
|
24,
|
||||||
"User Role Operation Not Supported",
|
self.get_error_message(),
|
||||||
None,
|
None,
|
||||||
)),
|
)),
|
||||||
Self::IpAddressParsingFailed => {
|
Self::InvalidMetadataRequest => {
|
||||||
AER::InternalServerError(ApiError::new(sub_code, 24, "Something Went Wrong", None))
|
AER::BadRequest(ApiError::new(sub_code, 26, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::InvalidMetadataRequest => AER::BadRequest(ApiError::new(
|
|
||||||
sub_code,
|
|
||||||
26,
|
|
||||||
"Invalid Metadata Request",
|
|
||||||
None,
|
|
||||||
)),
|
|
||||||
Self::MerchantIdParsingError => {
|
Self::MerchantIdParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 28, "Invalid Merchant Id", None))
|
AER::BadRequest(ApiError::new(sub_code, 28, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
Self::ChangePasswordError => AER::BadRequest(ApiError::new(
|
Self::ChangePasswordError => {
|
||||||
sub_code,
|
AER::BadRequest(ApiError::new(sub_code, 29, self.get_error_message(), None))
|
||||||
29,
|
}
|
||||||
"Old and new password cannot be same",
|
Self::InvalidDeleteOperation => {
|
||||||
None,
|
AER::BadRequest(ApiError::new(sub_code, 30, self.get_error_message(), None))
|
||||||
)),
|
}
|
||||||
Self::InvalidDeleteOperation => AER::BadRequest(ApiError::new(
|
Self::MaxInvitationsError => {
|
||||||
sub_code,
|
AER::BadRequest(ApiError::new(sub_code, 31, self.get_error_message(), None))
|
||||||
30,
|
}
|
||||||
"Delete Operation Not Supported",
|
}
|
||||||
None,
|
}
|
||||||
)),
|
}
|
||||||
|
|
||||||
|
impl UserErrors {
|
||||||
|
pub fn get_error_message(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::InternalServerError => "Something went wrong",
|
||||||
|
Self::InvalidCredentials => "Incorrect email or password",
|
||||||
|
Self::UserNotFound => "Email doesn’t exist. Register",
|
||||||
|
Self::UserExists => "An account already exists with this email",
|
||||||
|
Self::LinkInvalid => "Invalid or expired link",
|
||||||
|
Self::UnverifiedUser => "Kindly verify your account",
|
||||||
|
Self::InvalidOldPassword => "Old password incorrect. Please enter the correct password",
|
||||||
|
Self::EmailParsingError => "Invalid Email",
|
||||||
|
Self::NameParsingError => "Invalid Name",
|
||||||
|
Self::PasswordParsingError => "Invalid Password",
|
||||||
|
Self::UserAlreadyVerified => "User already verified",
|
||||||
|
Self::CompanyNameParsingError => "Invalid Company Name",
|
||||||
|
Self::MerchantAccountCreationError(error_message) => error_message,
|
||||||
|
Self::InvalidEmailError => "Invalid Email",
|
||||||
|
Self::MerchantIdNotFound => "Invalid Merchant ID",
|
||||||
|
Self::MetadataAlreadySet => "Metadata already set",
|
||||||
|
Self::DuplicateOrganizationId => "An Organization with the id already exists",
|
||||||
|
Self::InvalidRoleId => "Invalid Role ID",
|
||||||
|
Self::InvalidRoleOperation => "User Role Operation Not Supported",
|
||||||
|
Self::IpAddressParsingFailed => "Something went wrong",
|
||||||
|
Self::InvalidMetadataRequest => "Invalid Metadata Request",
|
||||||
|
Self::MerchantIdParsingError => "Invalid Merchant Id",
|
||||||
|
Self::ChangePasswordError => "Old and new password cannot be the same",
|
||||||
|
Self::InvalidDeleteOperation => "Delete Operation Not Supported",
|
||||||
|
Self::MaxInvitationsError => "Maximum invite count per request exceeded",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use api_models::user as user_api;
|
use api_models::user::{self as user_api, InviteMultipleUserResponse};
|
||||||
use diesel_models::{enums::UserStatus, user as storage_user, user_role::UserRoleNew};
|
use diesel_models::{enums::UserStatus, user as storage_user, user_role::UserRoleNew};
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use error_stack::IntoReport;
|
use error_stack::IntoReport;
|
||||||
@ -9,7 +9,7 @@ use router_env::env;
|
|||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use router_env::logger;
|
use router_env::logger;
|
||||||
|
|
||||||
use super::errors::{UserErrors, UserResponse};
|
use super::errors::{UserErrors, UserResponse, UserResult};
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use crate::services::email::types as email_types;
|
use crate::services::email::types as email_types;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -407,6 +407,12 @@ pub async fn invite_user(
|
|||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
let invitation_status = if cfg!(feature = "email") {
|
||||||
|
UserStatus::InvitationSent
|
||||||
|
} else {
|
||||||
|
UserStatus::Active
|
||||||
|
};
|
||||||
|
|
||||||
let now = common_utils::date_time::now();
|
let now = common_utils::date_time::now();
|
||||||
state
|
state
|
||||||
.store
|
.store
|
||||||
@ -415,7 +421,7 @@ pub async fn invite_user(
|
|||||||
merchant_id: user_from_token.merchant_id,
|
merchant_id: user_from_token.merchant_id,
|
||||||
role_id: request.role_id,
|
role_id: request.role_id,
|
||||||
org_id: user_from_token.org_id,
|
org_id: user_from_token.org_id,
|
||||||
status: UserStatus::InvitationSent,
|
status: invitation_status,
|
||||||
created_by: user_from_token.user_id.clone(),
|
created_by: user_from_token.user_id.clone(),
|
||||||
last_modified_by: user_from_token.user_id,
|
last_modified_by: user_from_token.user_id,
|
||||||
created_at: now,
|
created_at: now,
|
||||||
@ -467,6 +473,181 @@ pub async fn invite_user(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn invite_multiple_user(
|
||||||
|
state: AppState,
|
||||||
|
user_from_token: auth::UserFromToken,
|
||||||
|
requests: Vec<user_api::InviteUserRequest>,
|
||||||
|
) -> UserResponse<Vec<InviteMultipleUserResponse>> {
|
||||||
|
if requests.len() > 10 {
|
||||||
|
return Err(UserErrors::MaxInvitationsError.into())
|
||||||
|
.attach_printable("Number of invite requests must not exceed 10");
|
||||||
|
}
|
||||||
|
|
||||||
|
let responses = futures::future::join_all(requests.iter().map(|request| async {
|
||||||
|
match handle_invitation(&state, &user_from_token, request).await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(error) => InviteMultipleUserResponse {
|
||||||
|
email: request.email.clone(),
|
||||||
|
is_email_sent: false,
|
||||||
|
password: None,
|
||||||
|
error: Some(error.current_context().get_error_message().to_string()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::Json(responses))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_invitation(
|
||||||
|
state: &AppState,
|
||||||
|
user_from_token: &auth::UserFromToken,
|
||||||
|
request: &user_api::InviteUserRequest,
|
||||||
|
) -> UserResult<InviteMultipleUserResponse> {
|
||||||
|
let inviter_user = user_from_token.get_user(state.clone()).await?;
|
||||||
|
|
||||||
|
if inviter_user.email == request.email {
|
||||||
|
return Err(UserErrors::InvalidRoleOperation.into())
|
||||||
|
.attach_printable("User Inviting themself");
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::user_role::validate_role_id(request.role_id.as_str())?;
|
||||||
|
let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
|
||||||
|
let invitee_user = state
|
||||||
|
.store
|
||||||
|
.find_user_by_email(invitee_email.clone().get_secret().expose().as_str())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(invitee_user) = invitee_user {
|
||||||
|
handle_existing_user_invitation(state, user_from_token, request, invitee_user.into()).await
|
||||||
|
} else if invitee_user
|
||||||
|
.as_ref()
|
||||||
|
.map_err(|e| e.current_context().is_db_not_found())
|
||||||
|
.err()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
handle_new_user_invitation(state, user_from_token, request).await
|
||||||
|
} else {
|
||||||
|
Err(UserErrors::InternalServerError.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: send email
|
||||||
|
async fn handle_existing_user_invitation(
|
||||||
|
state: &AppState,
|
||||||
|
user_from_token: &auth::UserFromToken,
|
||||||
|
request: &user_api::InviteUserRequest,
|
||||||
|
invitee_user_from_db: domain::UserFromStorage,
|
||||||
|
) -> UserResult<InviteMultipleUserResponse> {
|
||||||
|
let now = common_utils::date_time::now();
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.insert_user_role(UserRoleNew {
|
||||||
|
user_id: invitee_user_from_db.get_user_id().to_owned(),
|
||||||
|
merchant_id: user_from_token.merchant_id.clone(),
|
||||||
|
role_id: request.role_id.clone(),
|
||||||
|
org_id: user_from_token.org_id.clone(),
|
||||||
|
status: UserStatus::Active,
|
||||||
|
created_by: user_from_token.user_id.clone(),
|
||||||
|
last_modified_by: user_from_token.user_id.clone(),
|
||||||
|
created_at: now,
|
||||||
|
last_modified: now,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
if e.current_context().is_db_unique_violation() {
|
||||||
|
e.change_context(UserErrors::UserExists)
|
||||||
|
} else {
|
||||||
|
e.change_context(UserErrors::InternalServerError)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(InviteMultipleUserResponse {
|
||||||
|
email: request.email.clone(),
|
||||||
|
is_email_sent: false,
|
||||||
|
password: None,
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_new_user_invitation(
|
||||||
|
state: &AppState,
|
||||||
|
user_from_token: &auth::UserFromToken,
|
||||||
|
request: &user_api::InviteUserRequest,
|
||||||
|
) -> UserResult<InviteMultipleUserResponse> {
|
||||||
|
let new_user = domain::NewUser::try_from((request.clone(), user_from_token.clone()))?;
|
||||||
|
|
||||||
|
new_user
|
||||||
|
.insert_user_in_db(state.store.as_ref())
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
let invitation_status = if cfg!(feature = "email") {
|
||||||
|
UserStatus::InvitationSent
|
||||||
|
} else {
|
||||||
|
UserStatus::Active
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = common_utils::date_time::now();
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.insert_user_role(UserRoleNew {
|
||||||
|
user_id: new_user.get_user_id().to_owned(),
|
||||||
|
merchant_id: user_from_token.merchant_id.clone(),
|
||||||
|
role_id: request.role_id.clone(),
|
||||||
|
org_id: user_from_token.org_id.clone(),
|
||||||
|
status: invitation_status,
|
||||||
|
created_by: user_from_token.user_id.clone(),
|
||||||
|
last_modified_by: user_from_token.user_id.clone(),
|
||||||
|
created_at: now,
|
||||||
|
last_modified: now,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
if e.current_context().is_db_unique_violation() {
|
||||||
|
e.change_context(UserErrors::UserExists)
|
||||||
|
} else {
|
||||||
|
e.change_context(UserErrors::InternalServerError)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let is_email_sent;
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
{
|
||||||
|
let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
|
||||||
|
let email_contents = email_types::InviteUser {
|
||||||
|
recipient_email: invitee_email,
|
||||||
|
user_name: domain::UserName::new(new_user.get_name())?,
|
||||||
|
settings: state.conf.clone(),
|
||||||
|
subject: "You have been invited to join Hyperswitch Community!",
|
||||||
|
};
|
||||||
|
let send_email_result = state
|
||||||
|
.email_client
|
||||||
|
.compose_and_send_email(
|
||||||
|
Box::new(email_contents),
|
||||||
|
state.conf.proxy.https_url.as_ref(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
logger::info!(?send_email_result);
|
||||||
|
is_email_sent = send_email_result.is_ok();
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "email"))]
|
||||||
|
{
|
||||||
|
is_email_sent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(InviteMultipleUserResponse {
|
||||||
|
is_email_sent,
|
||||||
|
password: if cfg!(not(feature = "email")) {
|
||||||
|
Some(new_user.get_password().get_secret())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
email: request.email.clone(),
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn create_internal_user(
|
pub async fn create_internal_user(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::CreateInternalUserRequest,
|
request: user_api::CreateInternalUserRequest,
|
||||||
|
|||||||
@ -970,6 +970,9 @@ impl User {
|
|||||||
.service(web::resource("/user/invite").route(web::post().to(invite_user)))
|
.service(web::resource("/user/invite").route(web::post().to(invite_user)))
|
||||||
.service(web::resource("/user/invite/accept").route(web::post().to(accept_invitation)))
|
.service(web::resource("/user/invite/accept").route(web::post().to(accept_invitation)))
|
||||||
.service(web::resource("/update").route(web::post().to(update_user_account_details)))
|
.service(web::resource("/update").route(web::post().to(update_user_account_details)))
|
||||||
|
.service(
|
||||||
|
web::resource("/user/invite_multiple").route(web::post().to(invite_multiple_user)),
|
||||||
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/data")
|
web::resource("/data")
|
||||||
.route(web::get().to(get_multiple_dashboard_metadata))
|
.route(web::get().to(get_multiple_dashboard_metadata))
|
||||||
|
|||||||
@ -176,6 +176,7 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::ForgotPassword
|
| Flow::ForgotPassword
|
||||||
| Flow::ResetPassword
|
| Flow::ResetPassword
|
||||||
| Flow::InviteUser
|
| Flow::InviteUser
|
||||||
|
| Flow::InviteMultipleUser
|
||||||
| Flow::DeleteUser
|
| Flow::DeleteUser
|
||||||
| Flow::UserSignUpWithMerchantId
|
| Flow::UserSignUpWithMerchantId
|
||||||
| Flow::VerifyEmail
|
| Flow::VerifyEmail
|
||||||
|
|||||||
@ -350,6 +350,23 @@ pub async fn invite_user(
|
|||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
pub async fn invite_multiple_user(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: web::Json<Vec<user_api::InviteUserRequest>>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::InviteMultipleUser;
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state.clone(),
|
||||||
|
&req,
|
||||||
|
payload.into_inner(),
|
||||||
|
user_core::invite_multiple_user,
|
||||||
|
&auth::JWTAuth(Permission::UsersWrite),
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
pub async fn verify_email(
|
pub async fn verify_email(
|
||||||
|
|||||||
@ -321,6 +321,8 @@ pub enum Flow {
|
|||||||
ResetPassword,
|
ResetPassword,
|
||||||
/// Invite users
|
/// Invite users
|
||||||
InviteUser,
|
InviteUser,
|
||||||
|
/// Invite multiple users
|
||||||
|
InviteMultipleUser,
|
||||||
/// Delete user
|
/// Delete user
|
||||||
DeleteUser,
|
DeleteUser,
|
||||||
/// Incremental Authorization flow
|
/// Incremental Authorization flow
|
||||||
|
|||||||
Reference in New Issue
Block a user