feat: identity: add&rm employee to store

This commit is contained in:
Aravinth Manivannan 2025-01-13 00:31:10 +05:30
parent 3595560058
commit 9d20e45489
Signed by: realaravinth
GPG key ID: F8F50389936984FF
13 changed files with 501 additions and 19 deletions

View file

@ -47,6 +47,9 @@ pub enum WebError {
DuplicateStoreName, DuplicateStoreName,
StoreIDNotFound, StoreIDNotFound,
DuplicateStoreID, DuplicateStoreID,
DuplicateRoleID,
DuplicateRoleName,
RoleIDNotFound,
} }
impl From<IdentityError> for WebError { impl From<IdentityError> for WebError {
@ -71,6 +74,9 @@ impl From<IdentityError> for WebError {
IdentityError::DuplicateStoreName => Self::DuplicateStoreName, IdentityError::DuplicateStoreName => Self::DuplicateStoreName,
IdentityError::StoreIDNotFound => Self::StoreIDNotFound, IdentityError::StoreIDNotFound => Self::StoreIDNotFound,
IdentityError::DuplicateStoreID => Self::DuplicateStoreID, IdentityError::DuplicateStoreID => Self::DuplicateStoreID,
IdentityError::DuplicateRoleID => Self::DuplicateRoleID,
IdentityError::DuplicateRoleName => Self::DuplicateRoleName,
IdentityError::RoleIDNotFound => Self::RoleIDNotFound,
} }
} }
} }
@ -99,6 +105,9 @@ impl ResponseError for WebError {
Self::DuplicateStoreName => StatusCode::BAD_REQUEST, Self::DuplicateStoreName => StatusCode::BAD_REQUEST,
Self::StoreIDNotFound => StatusCode::NOT_FOUND, Self::StoreIDNotFound => StatusCode::NOT_FOUND,
Self::DuplicateStoreID => StatusCode::INTERNAL_SERVER_ERROR, Self::DuplicateStoreID => StatusCode::INTERNAL_SERVER_ERROR,
Self::DuplicateRoleID => StatusCode::INTERNAL_SERVER_ERROR,
Self::DuplicateRoleName => StatusCode::BAD_REQUEST,
Self::RoleIDNotFound => StatusCode::BAD_REQUEST,
} }
} }
@ -127,6 +136,9 @@ impl ResponseError for WebError {
Self::DuplicateStoreName => HttpResponse::BadRequest().json(e), Self::DuplicateStoreName => HttpResponse::BadRequest().json(e),
Self::StoreIDNotFound => HttpResponse::NotFound().json(e), Self::StoreIDNotFound => HttpResponse::NotFound().json(e),
Self::DuplicateStoreID => HttpResponse::InternalServerError().json(e), Self::DuplicateStoreID => HttpResponse::InternalServerError().json(e),
Self::DuplicateRoleID => HttpResponse::InternalServerError().json(e),
Self::DuplicateRoleName => HttpResponse::BadRequest().json(e),
Self::RoleIDNotFound => HttpResponse::BadRequest().json(e),
} }
} }
} }

View file

@ -11,11 +11,13 @@ use sqlx::postgres::PgPool;
use crate::identity::{ use crate::identity::{
application::services::{IdentityServices, IdentityServicesObj}, application::services::{IdentityServices, IdentityServicesObj},
domain::{aggregate::User, employee_aggregate::Employee, store_aggregate::Store}, domain::{
aggregate::User, employee_aggregate::Employee, role_aggregate::Role, store_aggregate::Store,
},
}; };
use crate::settings::Settings; use crate::settings::Settings;
use output::{ use output::{
db::postgres::{employee_view, store_view, user_view, DBOutPostgresAdapter}, db::postgres::{employee_view, role_view, store_view, user_view, DBOutPostgresAdapter},
mailer::lettre::LettreMailer, mailer::lettre::LettreMailer,
phone::twilio::Phone, phone::twilio::Phone,
}; };
@ -48,6 +50,8 @@ pub fn load_adapters(pool: PgPool, settings: Settings) -> impl FnOnce(&mut web::
Arc::new(db.clone()), Arc::new(db.clone()),
Arc::new(db.clone()), Arc::new(db.clone()),
Arc::new(db.clone()), Arc::new(db.clone()),
Arc::new(db.clone()),
Arc::new(db.clone()),
Arc::new(mailer.clone()), Arc::new(mailer.clone()),
Arc::new(phone.clone()), Arc::new(phone.clone()),
Arc::new(phone.clone()), Arc::new(phone.clone()),
@ -60,11 +64,13 @@ pub fn load_adapters(pool: PgPool, settings: Settings) -> impl FnOnce(&mut web::
let (store_cqrs_exec, store_cqrs_query) = store_view::init_cqrs(db.clone(), services.clone()); let (store_cqrs_exec, store_cqrs_query) = store_view::init_cqrs(db.clone(), services.clone());
let (employee_cqrs_exec, employee_cqrs_query) = let (employee_cqrs_exec, employee_cqrs_query) =
employee_view::init_cqrs(db.clone(), services.clone()); employee_view::init_cqrs(db.clone(), services.clone());
let (role_cqrs_exec, role_cqrs_query) = role_view::init_cqrs(db.clone(), services.clone());
let identity_cqrs_exec = types::WebIdentityCqrsExec::new(Arc::new( let identity_cqrs_exec = types::WebIdentityCqrsExec::new(Arc::new(
types::IdentityCqrsExecBuilder::default() types::IdentityCqrsExecBuilder::default()
.user(user_cqrs_exec) .user(user_cqrs_exec)
.employee(employee_cqrs_exec) .employee(employee_cqrs_exec)
.role(role_cqrs_exec)
.store(store_cqrs_exec) .store(store_cqrs_exec)
.build() .build()
.unwrap(), .unwrap(),
@ -74,6 +80,7 @@ pub fn load_adapters(pool: PgPool, settings: Settings) -> impl FnOnce(&mut web::
cfg.configure(input::web::load_ctx()); cfg.configure(input::web::load_ctx());
cfg.app_data(Data::new(user_cqrs_query.clone())); cfg.app_data(Data::new(user_cqrs_query.clone()));
cfg.app_data(Data::new(role_cqrs_query.clone()));
cfg.app_data(Data::new(store_cqrs_query.clone())); cfg.app_data(Data::new(store_cqrs_query.clone()));
cfg.app_data(Data::new(employee_cqrs_query.clone())); cfg.app_data(Data::new(employee_cqrs_query.clone()));
cfg.app_data(identity_cqrs_exec.clone()); cfg.app_data(identity_cqrs_exec.clone());

View file

@ -17,12 +17,14 @@ use crate::identity::{
adapters::{ adapters::{
input::web::RoutesRepository, input::web::RoutesRepository,
output::db::postgres::{ output::db::postgres::{
employee_view::EmployeeView, store_view::StoreView, user_view::UserView, employee_view::EmployeeView, role_view::RoleView, store_view::StoreView,
DBOutPostgresAdapter, user_view::UserView, DBOutPostgresAdapter,
}, },
}, },
application::services::{errors::IdentityError, IdentityCommand, IdentityServicesObj}, application::services::{errors::IdentityError, IdentityCommand, IdentityServicesObj},
domain::{aggregate::User, employee_aggregate::Employee, store_aggregate::Store}, domain::{
aggregate::User, employee_aggregate::Employee, role_aggregate::Role, store_aggregate::Store,
},
}; };
pub type WebIdentityRoutesRepository = Data<Arc<RoutesRepository>>; pub type WebIdentityRoutesRepository = Data<Arc<RoutesRepository>>;
@ -56,6 +58,7 @@ pub struct IdentityCqrsExec {
user: IdentityUserCqrsExec, user: IdentityUserCqrsExec,
store: IdentityStoreCqrsExec, store: IdentityStoreCqrsExec,
employee: IdentityEmployeeCqrsExec, employee: IdentityEmployeeCqrsExec,
role: IdentityRoleCqrsExec,
} }
#[async_trait] #[async_trait]
@ -67,8 +70,13 @@ impl IdentityCqrsExecutor for IdentityCqrsExec {
) -> Result<(), AggregateError<IdentityError>> { ) -> Result<(), AggregateError<IdentityError>> {
self.user.execute(aggregate_id, command.clone()).await?; self.user.execute(aggregate_id, command.clone()).await?;
self.store.execute(aggregate_id, command.clone()).await?; self.store.execute(aggregate_id, command.clone()).await?;
self.role.execute(aggregate_id, command.clone()).await?;
self.employee.execute(aggregate_id, command).await?; self.employee.execute(aggregate_id, command).await?;
Ok(()) Ok(())
} }
} }
pub type IdentityRoleCqrsExec = Arc<PostgresCqrs<Role>>;
pub type IdentityRoleCqrsView = Arc<dyn ViewRepository<RoleView, Role>>;
pub type WebidentityRoleCqrsView = Data<IdentityRoleCqrsView>;

View file

@ -32,6 +32,9 @@ pub enum IdentityError {
DuplicateStoreName, DuplicateStoreName,
StoreIDNotFound, StoreIDNotFound,
DuplicateStoreID, DuplicateStoreID,
DuplicateRoleID,
DuplicateRoleName,
RoleIDNotFound,
} }
pub type IdentityCommandResult<V> = Result<V, IdentityCommandError>; pub type IdentityCommandResult<V> = Result<V, IdentityCommandError>;
@ -80,6 +83,8 @@ impl From<OutDBPortError> for IdentityError {
OutDBPortError::DuplicateStoreName => Self::DuplicateStoreName, OutDBPortError::DuplicateStoreName => Self::DuplicateStoreName,
OutDBPortError::DuplicateStoreID => Self::DuplicateStoreID, OutDBPortError::DuplicateStoreID => Self::DuplicateStoreID,
OutDBPortError::StoreIDNotFound => Self::StoreIDNotFound, OutDBPortError::StoreIDNotFound => Self::StoreIDNotFound,
OutDBPortError::RoleIDNotFound => Self::RoleIDNotFound,
OutDBPortError::DuplicateRoleName => Self::DuplicateRoleName,
} }
} }
} }

View file

@ -12,16 +12,11 @@ use super::update_email::events::*;
use super::update_password::events::*; use super::update_password::events::*;
use crate::identity::domain::{ use crate::identity::domain::{
employee_logged_in_event::*, employee_logged_in_event::*, employee_registered_event::*, login_otp_sent_event::*,
employee_registered_event::*, //invite_accepted_event::*, organization_exited_event::*, owner_added_employee_to_store_event::*,
login_otp_sent_event::*, owner_removed_employee_from_store_event::*, phone_number_changed_event::*,
organization_exited_event::*, phone_number_verified_event::*, resend_login_otp_event::*, role_added_event::*,
phone_number_changed_event::*, store_added_event::*, store_updated_event::*, verification_otp_resent_event::*,
phone_number_verified_event::*,
resend_login_otp_event::*,
store_added_event::*,
store_updated_event::*,
verification_otp_resent_event::*,
verification_otp_sent_event::*, verification_otp_sent_event::*,
}; };
@ -35,6 +30,9 @@ pub enum IdentityEvent {
UserVerified, UserVerified,
VerificationEmailResent, VerificationEmailResent,
UserPromotedToAdmin(UserPromotedToAdminEvent), UserPromotedToAdmin(UserPromotedToAdminEvent),
OwnerAddedEmployeeToStore(OwnerAddedEmployeeToStoreEvent),
OwnerRemovedEmployeeFromStore(OwnerRemovedEmployeeFromStoreEvent),
RoleAdded(RoleAddedEvent),
// employee // employee
EmployeeRegistered(EmployeeRegisteredEvent), EmployeeRegistered(EmployeeRegisteredEvent),
@ -70,6 +68,11 @@ impl DomainEvent for IdentityEvent {
IdentityEvent::UserVerified => "IdentityUserIsVerified", IdentityEvent::UserVerified => "IdentityUserIsVerified",
IdentityEvent::UserPromotedToAdmin { .. } => "IdentityUserPromotedToAdmin", IdentityEvent::UserPromotedToAdmin { .. } => "IdentityUserPromotedToAdmin",
IdentityEvent::VerificationEmailResent => "IdentityVerficationEmailResent", IdentityEvent::VerificationEmailResent => "IdentityVerficationEmailResent",
IdentityEvent::OwnerAddedEmployeeToStore { .. } => "IdentityOwnerAddedEmployeeToStore",
IdentityEvent::OwnerRemovedEmployeeFromStore { .. } => {
"IdentityOwnerRemovedEmployeeFromStore"
}
IdentityEvent::RoleAdded { .. } => "IdentityRoleAddedEvent",
// employee // employee
IdentityEvent::EmployeeRegistered { .. } => "EmployeeRegistered", IdentityEvent::EmployeeRegistered { .. } => "EmployeeRegistered",
IdentityEvent::EmployeeLoggedIn { .. } => "EmployeeLoggedIn", IdentityEvent::EmployeeLoggedIn { .. } => "EmployeeLoggedIn",

View file

@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
pub mod delete_user; pub mod delete_user;
//pub mod employee_accept_invite_service; //pub mod employee_accept_invite_service;
pub mod add_role_to_store_service;
pub mod employee_exit_organization_service; pub mod employee_exit_organization_service;
pub mod employee_login_service; pub mod employee_login_service;
pub mod employee_register_service; pub mod employee_register_service;
@ -21,11 +22,14 @@ pub mod errors;
pub mod events; pub mod events;
pub mod login; pub mod login;
pub mod mark_user_verified; pub mod mark_user_verified;
pub mod owner_manage_store_employee_service;
pub mod register_user; pub mod register_user;
pub mod resend_verification_email; pub mod resend_verification_email;
pub mod set_user_admin; pub mod set_user_admin;
pub mod update_email; pub mod update_email;
pub mod update_password; pub mod update_password;
//pub mod owner_set_role_to_employee_service;
//pub mod owner_remove_employee_from_role_service;
pub mod add_store_service; pub mod add_store_service;
pub mod update_store_service; pub mod update_store_service;
@ -33,6 +37,8 @@ pub mod update_store_service;
use add_store_service::*; use add_store_service::*;
use delete_user::{service::*, *}; use delete_user::{service::*, *};
//use employee_accept_invite_service::*; //use employee_accept_invite_service::*;
use add_role_to_store_service::*;
use add_role_to_store_service::*;
use employee_exit_organization_service::*; use employee_exit_organization_service::*;
use employee_login_service::*; use employee_login_service::*;
use employee_register_service::*; use employee_register_service::*;
@ -52,12 +58,15 @@ use errors::*;
use events::*; use events::*;
use crate::identity::domain::{ use crate::identity::domain::{
add_role_command::*,
// accept_invite_command::*, // accept_invite_command::*,
add_store_command::*, add_store_command::*,
change_phone_number_command::*, change_phone_number_command::*,
employee_login_command::*, employee_login_command::*,
employee_register_command::*, employee_register_command::*,
exit_organization_command::*, exit_organization_command::*,
owner_add_employee_to_store_command::*,
owner_remove_employee_from_store_command::*,
resend_login_otp_command::*, resend_login_otp_command::*,
resend_verification_otp_command::*, resend_verification_otp_command::*,
update_store_command::*, update_store_command::*,
@ -71,6 +80,7 @@ use crate::utils::{
use delete_user::command::*; use delete_user::command::*;
use login::command::*; use login::command::*;
use mark_user_verified::command::*; use mark_user_verified::command::*;
use owner_manage_store_employee_service::*;
use register_user::command::*; use register_user::command::*;
use resend_verification_email::command::*; use resend_verification_email::command::*;
use set_user_admin::command::*; use set_user_admin::command::*;
@ -82,8 +92,9 @@ use crate::identity::application::port::output::{
create_login_otp::*, create_verification_otp::*, create_verification_secret::*, create_login_otp::*, create_verification_otp::*, create_verification_secret::*,
delete_login_otp::*, delete_verification_otp::*, delete_verification_secret::*, delete_login_otp::*, delete_verification_otp::*, delete_verification_secret::*,
email_exists::*, emp_id_exists::*, get_emp_id_from_phone_number::*, get_login_otp::*, email_exists::*, emp_id_exists::*, get_emp_id_from_phone_number::*, get_login_otp::*,
get_verification_otp::*, get_verification_secret::*, phone_exists::*, store_id_exists::*, get_verification_otp::*, get_verification_secret::*, phone_exists::*, role_id_exists::*,
store_name_exists::*, user_id_exists::*, verification_secret_exists::*, role_name_exists_for_store::*, store_id_exists::*, store_name_exists::*, user_id_exists::*,
verification_secret_exists::*,
}, },
mailer::account_validation_link::*, mailer::account_validation_link::*,
phone::{account_login_otp::*, account_validation_otp::*}, phone::{account_login_otp::*, account_validation_otp::*},
@ -99,6 +110,9 @@ pub enum IdentityCommand {
MarkUserVerified(MarkUserVerifiedCommand), MarkUserVerified(MarkUserVerifiedCommand),
SetAdmin(SetAdminCommand), SetAdmin(SetAdminCommand),
ResendVerificationEmail(ResendVerificationEmailCommand), ResendVerificationEmail(ResendVerificationEmailCommand),
OwnerAddEmployeeToStore(OwnerAddEmployeeToStoreCommand),
OwnerRemoveEmployeeFromStore(OwnerRemoveEmployeeFromStoreCommand),
AddRole(AddRoleCommand),
// employee // employee
EmployeeRegister(EmployeeRegisterCommand), EmployeeRegister(EmployeeRegisterCommand),
EmployeeInitLogin(EmployeeInitLoginCommand), EmployeeInitLogin(EmployeeInitLoginCommand),
@ -124,6 +138,8 @@ pub trait IdentityServicesInterface: Send + Sync {
fn set_user_admin(&self) -> SetUserAdminServiceObj; fn set_user_admin(&self) -> SetUserAdminServiceObj;
fn update_email(&self) -> UpdateEmailServiceObj; fn update_email(&self) -> UpdateEmailServiceObj;
fn update_password(&self) -> UpdatePasswordServiceObj; fn update_password(&self) -> UpdatePasswordServiceObj;
fn owner_manage_employee(&self) -> OwnerManageStoreEmployeesServiceObj;
fn add_role_to_store(&self) -> AddRoleToStoreServiceObj;
// employee // employee
// fn employee_accept_invite_service(&self) -> EmployeeAcceptInviteServiceObj; // fn employee_accept_invite_service(&self) -> EmployeeAcceptInviteServiceObj;
@ -151,6 +167,8 @@ pub struct IdentityServices {
set_user_admin: SetUserAdminServiceObj, set_user_admin: SetUserAdminServiceObj,
update_email: UpdateEmailServiceObj, update_email: UpdateEmailServiceObj,
update_password: UpdatePasswordServiceObj, update_password: UpdatePasswordServiceObj,
owner_manage_store_employee: OwnerManageStoreEmployeesServiceObj,
add_role_to_store: AddRoleToStoreServiceObj,
// employee_accept_invite_service: EmployeeAcceptInviteServiceObj, // employee_accept_invite_service: EmployeeAcceptInviteServiceObj,
employee_exit_organization_service: EmployeeExitOrganizationServiceObj, employee_exit_organization_service: EmployeeExitOrganizationServiceObj,
@ -190,6 +208,14 @@ impl IdentityServicesInterface for IdentityServices {
self.update_password.clone() self.update_password.clone()
} }
fn owner_manage_employee(&self) -> OwnerManageStoreEmployeesServiceObj {
self.owner_manage_store_employee.clone()
}
fn add_role_to_store(&self) -> AddRoleToStoreServiceObj {
self.add_role_to_store.clone()
}
// employee // employee
// fn employee_accept_invite_service(&self) -> EmployeeAcceptInviteServiceObj { // fn employee_accept_invite_service(&self) -> EmployeeAcceptInviteServiceObj {
// self.employee_accept_invite_service.clone() // self.employee_accept_invite_service.clone()
@ -240,6 +266,8 @@ impl IdentityServices {
out_db_store_name_exists: StoreNameExistsDBPortObj, out_db_store_name_exists: StoreNameExistsDBPortObj,
out_db_user_id_exists: UserIDExistsOutDBPortObj, out_db_user_id_exists: UserIDExistsOutDBPortObj,
out_db_verification_secret_exists: VerificationSecretExistsOutDBPortObj, out_db_verification_secret_exists: VerificationSecretExistsOutDBPortObj,
out_db_role_id_exists: RoleIDExistsDBPortObj,
out_db_role_name_exists_for_store: RoleNameExistsForStoreDBPortObj,
out_mailer_account_validating_link: AccountValidationLinkOutMailerPortObj, out_mailer_account_validating_link: AccountValidationLinkOutMailerPortObj,
@ -295,6 +323,23 @@ impl IdentityServices {
let update_password: UpdatePasswordServiceObj = Arc::new(UpdatePasswordService); let update_password: UpdatePasswordServiceObj = Arc::new(UpdatePasswordService);
let owner_manage_store_employee: OwnerManageStoreEmployeesServiceObj = Arc::new(
OwnerManageStoreEmployeesServiceBuilder::default()
.db_store_id_exists_adapter(out_db_store_id_exists.clone())
.db_emp_id_exists_adapter(out_db_emp_id_exists.clone())
.build()
.unwrap(),
);
let add_role_to_store: AddRoleToStoreServiceObj = Arc::new(
AddRoleToStoreServiceBuilder::default()
.db_store_id_exists_adapter(out_db_store_id_exists.clone())
.db_role_id_exists_adapter(out_db_role_id_exists.clone())
.db_role_name_exists_for_store_adapter(out_db_role_name_exists_for_store.clone())
.build()
.unwrap(),
);
// let employee_accept_invite_service: EmployeeAcceptInviteServiceObj = Arc::new( // let employee_accept_invite_service: EmployeeAcceptInviteServiceObj = Arc::new(
// EmployeeAcceptInviteServiceBuilder::default() // EmployeeAcceptInviteServiceBuilder::default()
// .db_get_invite_adapter(unimplemented!()) // .db_get_invite_adapter(unimplemented!())
@ -397,6 +442,8 @@ impl IdentityServices {
set_user_admin, set_user_admin,
update_email, update_email,
update_password, update_password,
owner_manage_store_employee,
add_role_to_store,
// employee_accept_invite_service, // employee_accept_invite_service,
employee_exit_organization_service, employee_exit_organization_service,
@ -443,6 +490,8 @@ mod tests {
mock_store_name_exists_db_port_true(IS_NEVER_CALLED), mock_store_name_exists_db_port_true(IS_NEVER_CALLED),
mock_user_id_exists_db_port(IS_NEVER_CALLED, false), mock_user_id_exists_db_port(IS_NEVER_CALLED, false),
mock_verification_secret_exists_db_port(IS_NEVER_CALLED, false), mock_verification_secret_exists_db_port(IS_NEVER_CALLED, false),
mock_role_id_exists_db_port_true(IS_NEVER_CALLED),
mock_role_name_exists_for_store_db_port_true(IS_NEVER_CALLED),
mock_account_validation_link_mailer_port(IS_NEVER_CALLED), mock_account_validation_link_mailer_port(IS_NEVER_CALLED),
mock_account_validation_otp_phone_port(IS_NEVER_CALLED), mock_account_validation_otp_phone_port(IS_NEVER_CALLED),
mock_account_login_otp_phone_port(IS_NEVER_CALLED), mock_account_login_otp_phone_port(IS_NEVER_CALLED),

View file

@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use mockall::predicate::*;
use mockall::*;
use crate::identity::application::port::output::db::{emp_id_exists::*, store_id_exists::*};
use crate::identity::domain::{
owner_add_employee_to_store_command::*, owner_added_employee_to_store_event::*,
owner_remove_employee_from_store_command::*, owner_removed_employee_from_store_event::*,
};
use super::errors::*;
#[automock]
#[async_trait::async_trait]
pub trait OwnerManageStoreEmployeesUseCase: Send + Sync {
async fn add_employee_to_store(
&self,
cmd: OwnerAddEmployeeToStoreCommand,
) -> IdentityResult<OwnerAddedEmployeeToStoreEvent>;
async fn remove_employee_from_store(
&self,
cmd: OwnerRemoveEmployeeFromStoreCommand,
) -> IdentityResult<OwnerRemovedEmployeeFromStoreEvent>;
}
pub type OwnerManageStoreEmployeesServiceObj = std::sync::Arc<dyn OwnerManageStoreEmployeesUseCase>;
#[derive(Clone, Builder)]
pub struct OwnerManageStoreEmployeesService {
db_emp_id_exists_adapter: EmpIDExistsOutDBPortObj,
db_store_id_exists_adapter: StoreIDExistsDBPortObj,
}
#[async_trait::async_trait]
impl OwnerManageStoreEmployeesUseCase for OwnerManageStoreEmployeesService {
async fn remove_employee_from_store(
&self,
cmd: OwnerRemoveEmployeeFromStoreCommand,
) -> IdentityResult<OwnerRemovedEmployeeFromStoreEvent> {
if !self
.db_store_id_exists_adapter
.store_id_exists(cmd.store_id())
.await?
{
return Err(IdentityError::StoreNotFound);
}
if !self
.db_emp_id_exists_adapter
.emp_id_exists(cmd.emp_id())
.await?
{
return Err(IdentityError::EmployeeNotFound);
}
Ok(OwnerRemovedEmployeeFromStoreEventBuilder::default()
.emp_id(*cmd.emp_id())
.store_id(*cmd.store_id())
.added_by(*cmd.adding_by())
.build()
.unwrap())
}
async fn add_employee_to_store(
&self,
cmd: OwnerAddEmployeeToStoreCommand,
) -> IdentityResult<OwnerAddedEmployeeToStoreEvent> {
if !self
.db_store_id_exists_adapter
.store_id_exists(cmd.store_id())
.await?
{
return Err(IdentityError::StoreNotFound);
}
if !self
.db_emp_id_exists_adapter
.emp_id_exists(cmd.emp_id())
.await?
{
return Err(IdentityError::EmployeeNotFound);
}
Ok(OwnerAddedEmployeeToStoreEventBuilder::default()
.emp_id(*cmd.emp_id())
.store_id(*cmd.store_id())
.added_by(*cmd.adding_by())
.build()
.unwrap())
}
}
#[cfg(test)]
mod tests {
use crate::{tests::bdd::*, utils::uuid::tests::*};
use super::*;
impl OwnerManageStoreEmployeesService {
pub fn mock_service(
times: Option<usize>,
cmd: OwnerAddEmployeeToStoreCommand,
) -> OwnerManageStoreEmployeesServiceObj {
let res = OwnerAddedEmployeeToStoreEvent::get_event(&cmd);
let mut m = MockOwnerManageStoreEmployeesUseCase::default();
if let Some(times) = times {
m.expect_add_employee_to_store()
.times(times)
.returning(move |_| Ok(res.clone()));
} else {
m.expect_add_employee_to_store()
.returning(move |_| Ok(res.clone()));
}
std::sync::Arc::new(m)
}
}
#[actix_rt::test]
async fn test_service_add_employee() {
let s = OwnerManageStoreEmployeesServiceBuilder::default()
.db_emp_id_exists_adapter(mock_emp_id_exists_db_port(IS_CALLED_ONLY_ONCE, true))
.db_store_id_exists_adapter(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
.build()
.unwrap();
{
let cmd = OwnerAddEmployeeToStoreCommandBuilder::default()
.emp_id(UUID)
.store_id(UUID)
.adding_by(UUID)
.build()
.unwrap();
let res = s.add_employee_to_store(cmd.clone()).await.unwrap();
assert_eq!(*res.emp_id(), *cmd.emp_id());
assert_eq!(*res.added_by(), *cmd.adding_by());
assert_eq!(*res.store_id(), *cmd.store_id());
}
}
#[actix_rt::test]
async fn test_service_add_employee_emp_no_exist() {
let s = OwnerManageStoreEmployeesServiceBuilder::default()
.db_emp_id_exists_adapter(mock_emp_id_exists_db_port(IS_CALLED_ONLY_ONCE, false))
.db_store_id_exists_adapter(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
.build()
.unwrap();
{
let cmd = OwnerAddEmployeeToStoreCommandBuilder::default()
.emp_id(UUID)
.adding_by(UUID)
.store_id(UUID)
.build()
.unwrap();
assert_eq!(
s.add_employee_to_store(cmd.clone()).await.err(),
Some(IdentityError::EmployeeNotFound)
);
}
}
#[actix_rt::test]
async fn test_service_remove_employee() {
let s = OwnerManageStoreEmployeesServiceBuilder::default()
.db_emp_id_exists_adapter(mock_emp_id_exists_db_port(IS_CALLED_ONLY_ONCE, true))
.db_store_id_exists_adapter(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
.build()
.unwrap();
{
let cmd = OwnerRemoveEmployeeFromStoreCommand::get_cmd();
let res = s.remove_employee_from_store(cmd.clone()).await.unwrap();
assert_eq!(*res.emp_id(), *cmd.emp_id());
assert_eq!(*res.added_by(), *cmd.adding_by());
assert_eq!(*res.store_id(), *cmd.store_id());
}
}
#[actix_rt::test]
async fn test_service_remove_employee_emp_no_exist() {
let s = OwnerManageStoreEmployeesServiceBuilder::default()
.db_emp_id_exists_adapter(mock_emp_id_exists_db_port(IS_CALLED_ONLY_ONCE, false))
.db_store_id_exists_adapter(mock_store_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
.build()
.unwrap();
{
let cmd = OwnerRemoveEmployeeFromStoreCommand::get_cmd();
assert_eq!(
s.remove_employee_from_store(cmd.clone()).await.err(),
Some(IdentityError::EmployeeNotFound)
);
}
}
}

View file

@ -134,6 +134,20 @@ impl Aggregate for User {
.await?; .await?;
Ok(vec![IdentityEvent::VerificationEmailResent]) Ok(vec![IdentityEvent::VerificationEmailResent])
} }
IdentityCommand::OwnerAddEmployeeToStore(cmd) => {
let res = services
.owner_manage_employee()
.add_employee_to_store(cmd)
.await?;
Ok(vec![IdentityEvent::OwnerAddedEmployeeToStore(res)])
}
IdentityCommand::OwnerRemoveEmployeeFromStore(cmd) => {
let res = services
.owner_manage_employee()
.remove_employee_from_store(cmd)
.await?;
Ok(vec![IdentityEvent::OwnerRemovedEmployeeFromStore(res)])
}
_ => Ok(Vec::new()), _ => Ok(Vec::new()),
} }
} }

View file

@ -26,6 +26,8 @@ pub struct Employee {
#[builder(default = "None")] #[builder(default = "None")]
store_id: Option<Uuid>, store_id: Option<Uuid>,
deleted: bool, deleted: bool,
#[builder(default = "None")]
role_id: Option<Uuid>,
} }
#[derive( #[derive(
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder, Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
@ -62,6 +64,7 @@ impl Default for Employee {
first_name: "".to_string(), first_name: "".to_string(),
last_name: "".to_string(), last_name: "".to_string(),
phone_number: Default::default(), phone_number: Default::default(),
role_id: None,
phone_verified: false, phone_verified: false,
deleted: false, deleted: false,
emp_id: Uuid::new_v4(), emp_id: Uuid::new_v4(),
@ -160,7 +163,14 @@ impl Aggregate for Employee {
IdentityEvent::PhoneNumberChanged(e) => unimplemented!(), IdentityEvent::PhoneNumberChanged(e) => unimplemented!(),
// IdentityEvent::InviteAccepted(e) => self.store_id = Some(*e.store_id()), // IdentityEvent::InviteAccepted(e) => self.store_id = Some(*e.store_id()),
IdentityEvent::OrganizationExited(e) => self.store_id = None, IdentityEvent::OrganizationExited(e) => self.store_id = None,
IdentityEvent::OwnerAddedEmployeeToStore(e) => {
self.store_id = Some(*e.store_id());
self.role_id = None;
}
IdentityEvent::OwnerRemovedEmployeeFromStore(e) => {
self.store_id = None;
self.role_id = None;
}
_ => (), _ => (),
} }
} }
@ -185,8 +195,9 @@ mod tests {
employee_register_command::EmployeeRegisterCommand, employee_register_command::EmployeeRegisterCommand,
employee_registered_event::EmployeeRegisteredEvent, employee_registered_event::EmployeeRegisteredEvent,
exit_organization_command::ExitOrganizationCommand, exit_organization_command::ExitOrganizationCommand,
// invite_accepted_event::InviteAcceptedEvent,
organization_exited_event::OrganizationExitedEvent, organization_exited_event::OrganizationExitedEvent,
owner_add_employee_to_store_command::OwnerAddEmployeeToStoreCommand,
owner_added_employee_to_store_event::OwnerAddedEmployeeToStoreEvent,
phone_number_verified_event::PhoneNumberVerifiedEvent, phone_number_verified_event::PhoneNumberVerifiedEvent,
resend_login_otp_command::ResendLoginOTPCommand, resend_login_otp_command::ResendLoginOTPCommand,
resend_login_otp_event::ResentLoginOTPEvent, resend_login_otp_event::ResentLoginOTPEvent,
@ -368,4 +379,25 @@ mod tests {
.when(IdentityCommand::EmployeeExitOrganization(cmd)) .when(IdentityCommand::EmployeeExitOrganization(cmd))
.then_expect_events(vec![expected]); .then_expect_events(vec![expected]);
} }
#[test]
fn test_owner_added_employee_to_store() {
let cmd = OwnerAddEmployeeToStoreCommand::get_cmd();
let expected = OwnerAddedEmployeeToStoreEvent::get_event(&cmd);
let expected = IdentityEvent::OwnerAddedEmployeeToStore(expected);
// let mut services = MockIdentityServicesInterface::new();
// services
// .expect_employee_accept_invite_service()
// .times(IS_CALLED_ONLY_ONCE.unwrap())
// .return_const(EmployeeAcceptInviteService::mock_service(
// IS_CALLED_ONLY_ONCE,
// cmd.clone(),
// ));
//
// EmployeeTestFramework::with(Arc::new(services))
// .given_no_previous_events()
// .when(IdentityCommand::EmployeeAcceptInvite(cmd))
// .then_expect_events(vec![expected]);
}
} }

View file

@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::employee_aggregate::*;
#[derive(
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
)]
pub struct OwnerAddEmployeeToStoreCommand {
adding_by: Uuid,
emp_id: Uuid,
store_id: Uuid,
}
#[cfg(test)]
pub mod tests {
use crate::utils::uuid::tests::UUID;
use super::*;
impl OwnerAddEmployeeToStoreCommand {
pub fn get_cmd() -> Self {
Self {
emp_id: UUID,
adding_by: UUID,
store_id: UUID,
}
}
}
}

View file

@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::role_aggregate::*;
#[derive(
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
)]
pub struct OwnerAddedEmployeeToStoreEvent {
emp_id: Uuid,
added_by: Uuid,
store_id: Uuid,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
identity::domain::owner_add_employee_to_store_command::OwnerAddEmployeeToStoreCommand,
utils::uuid::tests::UUID,
};
impl OwnerAddedEmployeeToStoreEvent {
pub fn get_event(cmd: &OwnerAddEmployeeToStoreCommand) -> Self {
Self {
emp_id: *cmd.emp_id(),
added_by: *cmd.adding_by(),
store_id: *cmd.store_id(),
}
}
}
}

View file

@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::employee_aggregate::*;
#[derive(
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
)]
pub struct OwnerRemoveEmployeeFromStoreCommand {
adding_by: Uuid,
emp_id: Uuid,
store_id: Uuid,
}
#[cfg(test)]
pub mod tests {
use crate::utils::uuid::tests::UUID;
use super::*;
impl OwnerRemoveEmployeeFromStoreCommand {
pub fn get_cmd() -> Self {
Self {
emp_id: UUID,
adding_by: UUID,
store_id: UUID,
}
}
}
}

View file

@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use derive_builder::Builder;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::employee_aggregate::*;
#[derive(
Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder,
)]
pub struct OwnerRemovedEmployeeFromStoreEvent {
emp_id: Uuid,
added_by: Uuid,
store_id: Uuid,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
identity::domain::owner_remove_employee_from_store_command::OwnerRemoveEmployeeFromStoreCommand,
utils::uuid::tests::UUID,
};
impl OwnerRemovedEmployeeFromStoreEvent {
pub fn get_event(cmd: &OwnerRemoveEmployeeFromStoreCommand) -> Self {
Self {
emp_id: *cmd.emp_id(),
added_by: *cmd.adding_by(),
store_id: *cmd.store_id(),
}
}
}
}