diff --git a/src/identity/application/services/update_username/command.rs b/src/identity/application/services/update_username/command.rs new file mode 100644 index 0000000..a0f6cdb --- /dev/null +++ b/src/identity/application/services/update_username/command.rs @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use derive_getters::Getters; +use serde::{Deserialize, Serialize}; + +use super::*; + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Getters)] +pub struct UpdateUsernameCommand { + new_username: String, +} + +impl UpdateUsernameCommand { + pub fn new( + new_username: String, + supplied_password: String, + actual_password_hash: &str, + config: &argon2_creds::Config, + ) -> IdentityCommandResult { + if !argon2_creds::Config::verify(actual_password_hash, &supplied_password).unwrap() { + return Err(IdentityCommandError::WrongPassword); + } + let new_username = config.username(&new_username)?; + Ok(Self { new_username }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cmd() { + let config = argon2_creds::Config::default(); + let password = "adsfasdfasd"; + let new_username = "realaravinth"; + let hashed_password = config.password(password).unwrap(); + assert_eq!( + UpdateUsernameCommand::new( + new_username.into(), + password.into(), + &hashed_password, + &config + ) + .unwrap() + .new_username, + new_username + ); + + // username is not valid + assert!(matches!( + UpdateUsernameCommand::new( + "username".into(), + password.into(), + &hashed_password, + &config, + ) + .err(), + Some(IdentityCommandError::BadUsername(_)) + )); + + // wrong password + assert_eq!( + UpdateUsernameCommand::new( + new_username.into(), + new_username.into(), + &hashed_password, + &config, + ) + .err(), + Some(IdentityCommandError::WrongPassword) + ); + } +} diff --git a/src/identity/application/services/update_username/events.rs b/src/identity/application/services/update_username/events.rs new file mode 100644 index 0000000..a14a0f9 --- /dev/null +++ b/src/identity/application/services/update_username/events.rs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use derive_getters::Getters; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, Getters, Eq, PartialEq, Ord, PartialOrd)] +pub struct UsernameUpdatedEvent { + username: String, +} + +impl UsernameUpdatedEvent { + pub fn new(username: String) -> Self { + Self { username } + } +} diff --git a/src/identity/application/services/update_username/mod.rs b/src/identity/application/services/update_username/mod.rs new file mode 100644 index 0000000..64f6317 --- /dev/null +++ b/src/identity/application/services/update_username/mod.rs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +pub mod command; +pub mod events; +pub mod service; +use super::errors::*; + +#[async_trait::async_trait] +pub trait UpdateUsernameUseCase { + async fn update_username( + &self, + cmd: command::UpdateUsernameCommand, + ) -> IdentityResult; +} diff --git a/src/identity/application/services/update_username/service.rs b/src/identity/application/services/update_username/service.rs new file mode 100644 index 0000000..dda58bd --- /dev/null +++ b/src/identity/application/services/update_username/service.rs @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use super::*; +use derive_builder::Builder; + +use super::*; +use crate::identity::application::port::output::db::username_exists::*; + +#[derive(Builder)] +pub struct UpdateUsernameService { + db_username_exists_adapter: UsernameExistsOutDBPortObj, +} + +#[async_trait::async_trait] +impl UpdateUsernameUseCase for UpdateUsernameService { + async fn update_username( + &self, + cmd: command::UpdateUsernameCommand, + //) -> errors::ProcessAuthorizationServiceResult; + ) -> IdentityResult { + if self + .db_username_exists_adapter + .username_exists(cmd.new_username()) + .await + .unwrap() + { + return Err(IdentityError::UsernameExists); + } + + Ok(events::UsernameUpdatedEvent::new(cmd.new_username().into())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::bdd::*; + + #[actix_rt::test] + async fn test_service() { + let new_username = "realaravinth"; + let password = "password"; + let config = argon2_creds::Config::default(); + let hashed_password = config.password(password).unwrap(); + + let cmd = command::UpdateUsernameCommand::new( + new_username.into(), + password.into(), + &hashed_password, + &config, + ) + .unwrap(); + + { + let s = UpdateUsernameServiceBuilder::default() + .db_username_exists_adapter(mock_username_exists_db_port( + IS_CALLED_ONLY_ONCE, + RETURNS_FALSE, + )) + .build() + .unwrap(); + let res = s.update_username(cmd.clone()).await.unwrap(); + assert_eq!(res.username(), cmd.new_username()); + } + + { + let s = UpdateUsernameServiceBuilder::default() + .db_username_exists_adapter(mock_username_exists_db_port( + IS_CALLED_ONLY_ONCE, + RETURNS_TRUE, + )) + .build() + .unwrap(); + assert_eq!( + s.update_username(cmd.clone()).await.err(), + Some(IdentityError::UsernameExists) + ); + } + } +}