diff --git a/src/identity/adapters/input/web/errors.rs b/src/identity/adapters/input/web/errors.rs index 8321ee0..338608f 100644 --- a/src/identity/adapters/input/web/errors.rs +++ b/src/identity/adapters/input/web/errors.rs @@ -47,6 +47,9 @@ pub enum WebError { DuplicateStoreName, StoreIDNotFound, DuplicateStoreID, + DuplicateRoleID, + DuplicateRoleName, + RoleIDNotFound, } impl From for WebError { @@ -71,6 +74,9 @@ impl From for WebError { IdentityError::DuplicateStoreName => Self::DuplicateStoreName, IdentityError::StoreIDNotFound => Self::StoreIDNotFound, 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::StoreIDNotFound => StatusCode::NOT_FOUND, 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::StoreIDNotFound => HttpResponse::NotFound().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), } } } diff --git a/src/identity/adapters/mod.rs b/src/identity/adapters/mod.rs index 010baa2..988068e 100644 --- a/src/identity/adapters/mod.rs +++ b/src/identity/adapters/mod.rs @@ -11,11 +11,13 @@ use sqlx::postgres::PgPool; use crate::identity::{ 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 output::{ - db::postgres::{employee_view, store_view, user_view, DBOutPostgresAdapter}, + db::postgres::{employee_view, role_view, store_view, user_view, DBOutPostgresAdapter}, mailer::lettre::LettreMailer, 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(mailer.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 (employee_cqrs_exec, employee_cqrs_query) = 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( types::IdentityCqrsExecBuilder::default() .user(user_cqrs_exec) .employee(employee_cqrs_exec) + .role(role_cqrs_exec) .store(store_cqrs_exec) .build() .unwrap(), @@ -74,6 +80,7 @@ pub fn load_adapters(pool: PgPool, settings: Settings) -> impl FnOnce(&mut web:: cfg.configure(input::web::load_ctx()); 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(employee_cqrs_query.clone())); cfg.app_data(identity_cqrs_exec.clone()); diff --git a/src/identity/adapters/types.rs b/src/identity/adapters/types.rs index f2a1d47..b5ab808 100644 --- a/src/identity/adapters/types.rs +++ b/src/identity/adapters/types.rs @@ -17,12 +17,14 @@ use crate::identity::{ adapters::{ input::web::RoutesRepository, output::db::postgres::{ - employee_view::EmployeeView, store_view::StoreView, user_view::UserView, - DBOutPostgresAdapter, + employee_view::EmployeeView, role_view::RoleView, store_view::StoreView, + user_view::UserView, DBOutPostgresAdapter, }, }, 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>; @@ -56,6 +58,7 @@ pub struct IdentityCqrsExec { user: IdentityUserCqrsExec, store: IdentityStoreCqrsExec, employee: IdentityEmployeeCqrsExec, + role: IdentityRoleCqrsExec, } #[async_trait] @@ -67,8 +70,13 @@ impl IdentityCqrsExecutor for IdentityCqrsExec { ) -> Result<(), AggregateError> { self.user.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?; Ok(()) } } + +pub type IdentityRoleCqrsExec = Arc>; +pub type IdentityRoleCqrsView = Arc>; +pub type WebidentityRoleCqrsView = Data; diff --git a/src/identity/application/services/errors.rs b/src/identity/application/services/errors.rs index 253a70b..e4d9080 100644 --- a/src/identity/application/services/errors.rs +++ b/src/identity/application/services/errors.rs @@ -32,6 +32,9 @@ pub enum IdentityError { DuplicateStoreName, StoreIDNotFound, DuplicateStoreID, + DuplicateRoleID, + DuplicateRoleName, + RoleIDNotFound, } pub type IdentityCommandResult = Result; @@ -80,6 +83,8 @@ impl From for IdentityError { OutDBPortError::DuplicateStoreName => Self::DuplicateStoreName, OutDBPortError::DuplicateStoreID => Self::DuplicateStoreID, OutDBPortError::StoreIDNotFound => Self::StoreIDNotFound, + OutDBPortError::RoleIDNotFound => Self::RoleIDNotFound, + OutDBPortError::DuplicateRoleName => Self::DuplicateRoleName, } } } diff --git a/src/identity/application/services/events.rs b/src/identity/application/services/events.rs index 21f930d..e2435c0 100644 --- a/src/identity/application/services/events.rs +++ b/src/identity/application/services/events.rs @@ -12,16 +12,11 @@ use super::update_email::events::*; use super::update_password::events::*; use crate::identity::domain::{ - employee_logged_in_event::*, - employee_registered_event::*, //invite_accepted_event::*, - login_otp_sent_event::*, - organization_exited_event::*, - phone_number_changed_event::*, - phone_number_verified_event::*, - resend_login_otp_event::*, - store_added_event::*, - store_updated_event::*, - verification_otp_resent_event::*, + employee_logged_in_event::*, employee_registered_event::*, login_otp_sent_event::*, + organization_exited_event::*, owner_added_employee_to_store_event::*, + owner_removed_employee_from_store_event::*, phone_number_changed_event::*, + phone_number_verified_event::*, resend_login_otp_event::*, role_added_event::*, + store_added_event::*, store_updated_event::*, verification_otp_resent_event::*, verification_otp_sent_event::*, }; @@ -35,6 +30,9 @@ pub enum IdentityEvent { UserVerified, VerificationEmailResent, UserPromotedToAdmin(UserPromotedToAdminEvent), + OwnerAddedEmployeeToStore(OwnerAddedEmployeeToStoreEvent), + OwnerRemovedEmployeeFromStore(OwnerRemovedEmployeeFromStoreEvent), + RoleAdded(RoleAddedEvent), // employee EmployeeRegistered(EmployeeRegisteredEvent), @@ -70,6 +68,11 @@ impl DomainEvent for IdentityEvent { IdentityEvent::UserVerified => "IdentityUserIsVerified", IdentityEvent::UserPromotedToAdmin { .. } => "IdentityUserPromotedToAdmin", IdentityEvent::VerificationEmailResent => "IdentityVerficationEmailResent", + IdentityEvent::OwnerAddedEmployeeToStore { .. } => "IdentityOwnerAddedEmployeeToStore", + IdentityEvent::OwnerRemovedEmployeeFromStore { .. } => { + "IdentityOwnerRemovedEmployeeFromStore" + } + IdentityEvent::RoleAdded { .. } => "IdentityRoleAddedEvent", // employee IdentityEvent::EmployeeRegistered { .. } => "EmployeeRegistered", IdentityEvent::EmployeeLoggedIn { .. } => "EmployeeLoggedIn", diff --git a/src/identity/application/services/mod.rs b/src/identity/application/services/mod.rs index d11ba06..16adf7e 100644 --- a/src/identity/application/services/mod.rs +++ b/src/identity/application/services/mod.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; pub mod delete_user; //pub mod employee_accept_invite_service; +pub mod add_role_to_store_service; pub mod employee_exit_organization_service; pub mod employee_login_service; pub mod employee_register_service; @@ -21,11 +22,14 @@ pub mod errors; pub mod events; pub mod login; pub mod mark_user_verified; +pub mod owner_manage_store_employee_service; pub mod register_user; pub mod resend_verification_email; pub mod set_user_admin; pub mod update_email; 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 update_store_service; @@ -33,6 +37,8 @@ pub mod update_store_service; use add_store_service::*; use delete_user::{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_login_service::*; use employee_register_service::*; @@ -52,12 +58,15 @@ use errors::*; use events::*; use crate::identity::domain::{ + add_role_command::*, // accept_invite_command::*, add_store_command::*, change_phone_number_command::*, employee_login_command::*, employee_register_command::*, exit_organization_command::*, + owner_add_employee_to_store_command::*, + owner_remove_employee_from_store_command::*, resend_login_otp_command::*, resend_verification_otp_command::*, update_store_command::*, @@ -71,6 +80,7 @@ use crate::utils::{ use delete_user::command::*; use login::command::*; use mark_user_verified::command::*; +use owner_manage_store_employee_service::*; use register_user::command::*; use resend_verification_email::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::*, delete_login_otp::*, delete_verification_otp::*, delete_verification_secret::*, 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::*, - store_name_exists::*, user_id_exists::*, verification_secret_exists::*, + get_verification_otp::*, get_verification_secret::*, phone_exists::*, role_id_exists::*, + role_name_exists_for_store::*, store_id_exists::*, store_name_exists::*, user_id_exists::*, + verification_secret_exists::*, }, mailer::account_validation_link::*, phone::{account_login_otp::*, account_validation_otp::*}, @@ -99,6 +110,9 @@ pub enum IdentityCommand { MarkUserVerified(MarkUserVerifiedCommand), SetAdmin(SetAdminCommand), ResendVerificationEmail(ResendVerificationEmailCommand), + OwnerAddEmployeeToStore(OwnerAddEmployeeToStoreCommand), + OwnerRemoveEmployeeFromStore(OwnerRemoveEmployeeFromStoreCommand), + AddRole(AddRoleCommand), // employee EmployeeRegister(EmployeeRegisterCommand), EmployeeInitLogin(EmployeeInitLoginCommand), @@ -124,6 +138,8 @@ pub trait IdentityServicesInterface: Send + Sync { fn set_user_admin(&self) -> SetUserAdminServiceObj; fn update_email(&self) -> UpdateEmailServiceObj; fn update_password(&self) -> UpdatePasswordServiceObj; + fn owner_manage_employee(&self) -> OwnerManageStoreEmployeesServiceObj; + fn add_role_to_store(&self) -> AddRoleToStoreServiceObj; // employee // fn employee_accept_invite_service(&self) -> EmployeeAcceptInviteServiceObj; @@ -151,6 +167,8 @@ pub struct IdentityServices { set_user_admin: SetUserAdminServiceObj, update_email: UpdateEmailServiceObj, update_password: UpdatePasswordServiceObj, + owner_manage_store_employee: OwnerManageStoreEmployeesServiceObj, + add_role_to_store: AddRoleToStoreServiceObj, // employee_accept_invite_service: EmployeeAcceptInviteServiceObj, employee_exit_organization_service: EmployeeExitOrganizationServiceObj, @@ -190,6 +208,14 @@ impl IdentityServicesInterface for IdentityServices { 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 // fn employee_accept_invite_service(&self) -> EmployeeAcceptInviteServiceObj { // self.employee_accept_invite_service.clone() @@ -240,6 +266,8 @@ impl IdentityServices { out_db_store_name_exists: StoreNameExistsDBPortObj, out_db_user_id_exists: UserIDExistsOutDBPortObj, 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, @@ -295,6 +323,23 @@ impl IdentityServices { 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( // EmployeeAcceptInviteServiceBuilder::default() // .db_get_invite_adapter(unimplemented!()) @@ -397,6 +442,8 @@ impl IdentityServices { set_user_admin, update_email, update_password, + owner_manage_store_employee, + add_role_to_store, // employee_accept_invite_service, employee_exit_organization_service, @@ -443,6 +490,8 @@ mod tests { mock_store_name_exists_db_port_true(IS_NEVER_CALLED), mock_user_id_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_otp_phone_port(IS_NEVER_CALLED), mock_account_login_otp_phone_port(IS_NEVER_CALLED), diff --git a/src/identity/application/services/owner_manage_store_employee_service.rs b/src/identity/application/services/owner_manage_store_employee_service.rs new file mode 100644 index 0000000..65200ab --- /dev/null +++ b/src/identity/application/services/owner_manage_store_employee_service.rs @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// 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; + async fn remove_employee_from_store( + &self, + cmd: OwnerRemoveEmployeeFromStoreCommand, + ) -> IdentityResult; +} + +pub type OwnerManageStoreEmployeesServiceObj = std::sync::Arc; + +#[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 { + 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 { + 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, + 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) + ); + } + } +} diff --git a/src/identity/domain/aggregate.rs b/src/identity/domain/aggregate.rs index d4752cb..0110bf0 100644 --- a/src/identity/domain/aggregate.rs +++ b/src/identity/domain/aggregate.rs @@ -134,6 +134,20 @@ impl Aggregate for User { .await?; 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()), } } diff --git a/src/identity/domain/employee_aggregate.rs b/src/identity/domain/employee_aggregate.rs index 6214ec6..d0364fa 100644 --- a/src/identity/domain/employee_aggregate.rs +++ b/src/identity/domain/employee_aggregate.rs @@ -26,6 +26,8 @@ pub struct Employee { #[builder(default = "None")] store_id: Option, deleted: bool, + #[builder(default = "None")] + role_id: Option, } #[derive( Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters, Builder, @@ -62,6 +64,7 @@ impl Default for Employee { first_name: "".to_string(), last_name: "".to_string(), phone_number: Default::default(), + role_id: None, phone_verified: false, deleted: false, emp_id: Uuid::new_v4(), @@ -160,7 +163,14 @@ impl Aggregate for Employee { IdentityEvent::PhoneNumberChanged(e) => unimplemented!(), // IdentityEvent::InviteAccepted(e) => self.store_id = Some(*e.store_id()), 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_registered_event::EmployeeRegisteredEvent, exit_organization_command::ExitOrganizationCommand, - // invite_accepted_event::InviteAcceptedEvent, organization_exited_event::OrganizationExitedEvent, + owner_add_employee_to_store_command::OwnerAddEmployeeToStoreCommand, + owner_added_employee_to_store_event::OwnerAddedEmployeeToStoreEvent, phone_number_verified_event::PhoneNumberVerifiedEvent, resend_login_otp_command::ResendLoginOTPCommand, resend_login_otp_event::ResentLoginOTPEvent, @@ -368,4 +379,25 @@ mod tests { .when(IdentityCommand::EmployeeExitOrganization(cmd)) .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]); + } } diff --git a/src/identity/domain/owner_add_employee_to_store_command.rs b/src/identity/domain/owner_add_employee_to_store_command.rs new file mode 100644 index 0000000..154dcbb --- /dev/null +++ b/src/identity/domain/owner_add_employee_to_store_command.rs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// 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, + } + } + } +} diff --git a/src/identity/domain/owner_added_employee_to_store_event.rs b/src/identity/domain/owner_added_employee_to_store_event.rs new file mode 100644 index 0000000..3f2692f --- /dev/null +++ b/src/identity/domain/owner_added_employee_to_store_event.rs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// 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(), + } + } + } +} diff --git a/src/identity/domain/owner_remove_employee_from_store_command.rs b/src/identity/domain/owner_remove_employee_from_store_command.rs new file mode 100644 index 0000000..64d3b4f --- /dev/null +++ b/src/identity/domain/owner_remove_employee_from_store_command.rs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// 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, + } + } + } +} diff --git a/src/identity/domain/owner_removed_employee_from_store_event.rs b/src/identity/domain/owner_removed_employee_from_store_event.rs new file mode 100644 index 0000000..56244d5 --- /dev/null +++ b/src/identity/domain/owner_removed_employee_from_store_event.rs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// 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(), + } + } + } +}