diff --git a/src/inventory/adapters/output/db/postgres/category_id_exists.rs b/src/inventory/adapters/output/db/postgres/category_id_exists.rs index 6b04fa1..bf9c7ce 100644 --- a/src/inventory/adapters/output/db/postgres/category_id_exists.rs +++ b/src/inventory/adapters/output/db/postgres/category_id_exists.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use uuid::Uuid; use super::InventoryDBPostgresAdapter; diff --git a/src/inventory/adapters/output/db/postgres/customization_id_exists.rs b/src/inventory/adapters/output/db/postgres/customization_id_exists.rs index bdcf734..976f011 100644 --- a/src/inventory/adapters/output/db/postgres/customization_id_exists.rs +++ b/src/inventory/adapters/output/db/postgres/customization_id_exists.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use uuid::Uuid; use super::InventoryDBPostgresAdapter; diff --git a/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs b/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs index 8e2aa35..9f7dbb8 100644 --- a/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs +++ b/src/inventory/adapters/output/db/postgres/customization_name_exists_for_product.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use uuid::Uuid; use super::InventoryDBPostgresAdapter; diff --git a/src/inventory/adapters/output/db/postgres/get_category.rs b/src/inventory/adapters/output/db/postgres/get_category.rs new file mode 100644 index 0000000..13ca8fa --- /dev/null +++ b/src/inventory/adapters/output/db/postgres/get_category.rs @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use uuid::Uuid; + +use super::errors::*; +use super::InventoryDBPostgresAdapter; +use crate::inventory::application::port::output::db::{errors::*, get_category::*}; +use crate::inventory::domain::category_aggregate::*; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct InnerCategory { + name: String, + description: Option, + store_id: Uuid, + category_id: Uuid, + deleted: bool, +} + +impl From for Category { + fn from(v: InnerCategory) -> Self { + CategoryBuilder::default() + .name(v.name) + .description(v.description) + .store_id(v.store_id) + .category_id(v.category_id) + .deleted(v.deleted) + .build() + .unwrap() + } +} + +#[async_trait::async_trait] +impl GetCategoryDBPort for InventoryDBPostgresAdapter { + async fn get_category(&self, category_id: &Uuid) -> InventoryDBResult { + let res = sqlx::query_as!( + InnerCategory, + "SELECT + name, description, store_id, category_id, deleted + FROM + cqrs_inventory_category_query + WHERE + category_id = $1;", + category_id, + ) + .fetch_one(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, InventoryDBError::CategoryIDNotFound))?; + + Ok(res.into()) + } +} + +#[cfg(test)] +mod tests { + use uuid::Uuid; + + use crate::inventory::adapters::output::db::postgres::category_name_exists_for_store::tests::create_dummy_category_record; + + use super::*; + + #[actix_rt::test] + async fn test_postgres() { + let category_id = Uuid::new_v4(); + let store_id = Uuid::new_v4(); + let settings = crate::settings::tests::get_settings().await; + settings.create_db().await; + let db = super::InventoryDBPostgresAdapter::new( + sqlx::postgres::PgPool::connect(&settings.database.url) + .await + .unwrap(), + ); + + let category = CategoryBuilder::default() + .name("category_name".into()) + .description(Some("category_description".into())) + .category_id(category_id) + .store_id(store_id) + .build() + .unwrap(); + + // state doesn't exist + assert_eq!( + db.get_category(category.category_id()).await, + Err(InventoryDBError::CategoryIDNotFound) + ); + + create_dummy_category_record(&category, &db).await; + + // state exists + assert_eq!( + db.get_category(category.category_id()).await.unwrap(), + category + ); + settings.drop_db().await; + } +} diff --git a/src/inventory/adapters/output/db/postgres/mod.rs b/src/inventory/adapters/output/db/postgres/mod.rs index 550d113..647268c 100644 --- a/src/inventory/adapters/output/db/postgres/mod.rs +++ b/src/inventory/adapters/output/db/postgres/mod.rs @@ -15,6 +15,7 @@ mod customization_id_exists; mod customization_name_exists_for_product; mod customization_view; mod errors; +mod get_category; mod product_id_exists; mod product_name_exists_for_category; mod product_view; diff --git a/src/inventory/adapters/output/db/postgres/product_id_exists.rs b/src/inventory/adapters/output/db/postgres/product_id_exists.rs index 99294e2..63b1c35 100644 --- a/src/inventory/adapters/output/db/postgres/product_id_exists.rs +++ b/src/inventory/adapters/output/db/postgres/product_id_exists.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use uuid::Uuid; use super::InventoryDBPostgresAdapter; diff --git a/src/inventory/adapters/output/db/postgres/store_id_exists.rs b/src/inventory/adapters/output/db/postgres/store_id_exists.rs index 8cdcc06..c333ceb 100644 --- a/src/inventory/adapters/output/db/postgres/store_id_exists.rs +++ b/src/inventory/adapters/output/db/postgres/store_id_exists.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use uuid::Uuid; use super::InventoryDBPostgresAdapter; diff --git a/src/inventory/adapters/output/mod.rs b/src/inventory/adapters/output/mod.rs index 4589484..d965892 100644 --- a/src/inventory/adapters/output/mod.rs +++ b/src/inventory/adapters/output/mod.rs @@ -3,3 +3,4 @@ // SPDX-License-Identifier: AGPL-3.0-or-later mod db; +mod full_text_search; diff --git a/src/inventory/application/port/output/db/errors.rs b/src/inventory/application/port/output/db/errors.rs index df3ddfb..ef8b431 100644 --- a/src/inventory/application/port/output/db/errors.rs +++ b/src/inventory/application/port/output/db/errors.rs @@ -18,4 +18,8 @@ pub enum InventoryDBError { DuplicateCustomizationID, DuplicateCustomizationName, InternalError, + ProductIDNotFound, + CategoryIDNotFound, + CustomizationIDNotFound, + StoreIDNotFound, } diff --git a/src/inventory/application/port/output/db/get_category.rs b/src/inventory/application/port/output/db/get_category.rs new file mode 100644 index 0000000..61c5dde --- /dev/null +++ b/src/inventory/application/port/output/db/get_category.rs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use mockall::predicate::*; +use mockall::*; +use uuid::Uuid; + +use crate::inventory::domain::category_aggregate::Category; + +use super::errors::*; +#[cfg(test)] +#[allow(unused_imports)] +pub use tests::*; + +#[automock] +#[async_trait::async_trait] +pub trait GetCategoryDBPort: Send + Sync { + async fn get_category(&self, category_id: &Uuid) -> InventoryDBResult; +} + +pub type GetCategoryDBPortObj = std::sync::Arc; + +#[cfg(test)] +pub mod tests { + + use super::*; + + use std::sync::Arc; + + pub fn mock_get_category_db_port(times: Option) -> GetCategoryDBPortObj { + let mut m = MockGetCategoryDBPort::new(); + if let Some(times) = times { + m.expect_get_category() + .times(times) + .returning(|_| Ok(Category::default())); + } else { + m.expect_get_category() + .returning(|_| Ok(Category::default())); + } + + Arc::new(m) + } +} diff --git a/src/inventory/application/port/output/db/mod.rs b/src/inventory/application/port/output/db/mod.rs index 3833d54..59ff4ca 100644 --- a/src/inventory/application/port/output/db/mod.rs +++ b/src/inventory/application/port/output/db/mod.rs @@ -7,6 +7,7 @@ pub mod category_name_exists_for_store; pub mod customization_id_exists; pub mod customization_name_exists_for_product; pub mod errors; +pub mod get_category; pub mod product_id_exists; pub mod product_name_exists_for_category; pub mod store_id_exists; diff --git a/src/inventory/application/services/add_product_service.rs b/src/inventory/application/services/add_product_service.rs index a446bc9..e446886 100644 --- a/src/inventory/application/services/add_product_service.rs +++ b/src/inventory/application/services/add_product_service.rs @@ -10,8 +10,12 @@ use mockall::*; use super::errors::*; use crate::inventory::{ - application::port::output::db::{ - category_id_exists::*, product_id_exists::*, product_name_exists_for_category::*, + application::port::output::{ + db::{ + category_id_exists::*, get_category::*, product_id_exists::*, + product_name_exists_for_category::*, + }, + full_text_search::add_product_to_store::*, }, domain::{ add_product_command::AddProductCommand, @@ -34,6 +38,8 @@ pub struct AddProductService { db_category_id_exists: CategoryIDExistsDBPortObj, db_product_name_exists_for_category: ProductNameExistsForCategoryDBPortObj, db_product_id_exists: ProductIDExistsDBPortObj, + db_get_category: GetCategoryDBPortObj, + fts_add_product: AddProductToStoreFTSPortObj, get_uuid: GetUUIDInterfaceObj, } @@ -83,6 +89,14 @@ impl AddProductUseCase for AddProductService { return Err(InventoryError::DuplicateProductName); } + let category = self + .db_get_category + .get_category(product.category_id()) + .await?; + self.fts_add_product + .add_product_to_store(&product, &category) + .await?; + Ok(ProductAddedEventBuilder::default() .added_by_user(*cmd.adding_by()) .name(product.name().into()) @@ -148,7 +162,9 @@ pub mod tests { mock_product_name_exists_for_category_db_port_false(IS_CALLED_ONLY_ONCE), ) .db_product_id_exists(mock_product_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) + .db_get_category(mock_get_category_db_port(IS_CALLED_ONLY_ONCE)) .db_category_id_exists(mock_category_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) + .fts_add_product(mock_add_product_to_store_fts_port(IS_CALLED_ONLY_ONCE)) .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .build() .unwrap(); @@ -176,6 +192,8 @@ pub mod tests { .db_category_id_exists(mock_category_id_exists_db_port_true(IS_CALLED_ONLY_ONCE)) .get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE)) .db_product_id_exists(mock_product_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) + .db_get_category(mock_get_category_db_port(IS_NEVER_CALLED)) + .fts_add_product(mock_add_product_to_store_fts_port(IS_NEVER_CALLED)) .build() .unwrap(); @@ -195,6 +213,8 @@ pub mod tests { ) .db_product_id_exists(mock_product_id_exists_db_port_false(IS_NEVER_CALLED)) .db_category_id_exists(mock_category_id_exists_db_port_false(IS_CALLED_ONLY_ONCE)) + .db_get_category(mock_get_category_db_port(IS_NEVER_CALLED)) + .fts_add_product(mock_add_product_to_store_fts_port(IS_NEVER_CALLED)) .get_uuid(mock_get_uuid(IS_NEVER_CALLED)) .build() .unwrap(); diff --git a/src/inventory/application/services/errors.rs b/src/inventory/application/services/errors.rs index 24c848b..703a9e2 100644 --- a/src/inventory/application/services/errors.rs +++ b/src/inventory/application/services/errors.rs @@ -6,7 +6,9 @@ use derive_more::{Display, Error}; use log::error; use serde::{Deserialize, Serialize}; -use crate::inventory::application::port::output::db::errors::InventoryDBError; +use crate::inventory::application::port::output::{ + db::errors::InventoryDBError, full_text_search::errors::InventoryFTSError, +}; pub type InventoryResult = Result; @@ -47,6 +49,17 @@ impl From for InventoryError { Self::InternalError } InventoryDBError::InternalError => Self::InternalError, + InventoryDBError::ProductIDNotFound => InventoryError::ProductIDNotFound, + InventoryDBError::CategoryIDNotFound => InventoryError::CategoryIDNotFound, + InventoryDBError::CustomizationIDNotFound => InventoryError::CustomizationIDNotFound, + InventoryDBError::StoreIDNotFound => InventoryError::StoreIDNotFound, } } } + +impl From for InventoryError { + fn from(value: InventoryFTSError) -> Self { + error!("{}", value); + InventoryError::InternalError + } +} diff --git a/src/inventory/application/services/update_category_service.rs b/src/inventory/application/services/update_category_service.rs index f0297d5..be6f876 100644 --- a/src/inventory/application/services/update_category_service.rs +++ b/src/inventory/application/services/update_category_service.rs @@ -1,6 +1,7 @@ -/// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use std::sync::Arc; use derive_builder::Builder; diff --git a/src/inventory/application/services/update_customization_service.rs b/src/inventory/application/services/update_customization_service.rs index 3a0c6ad..8c04823 100644 --- a/src/inventory/application/services/update_customization_service.rs +++ b/src/inventory/application/services/update_customization_service.rs @@ -1,6 +1,7 @@ -/// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use std::sync::Arc; use derive_builder::Builder; diff --git a/src/inventory/application/services/update_product_service.rs b/src/inventory/application/services/update_product_service.rs index b36af6a..adb363e 100644 --- a/src/inventory/application/services/update_product_service.rs +++ b/src/inventory/application/services/update_product_service.rs @@ -1,6 +1,7 @@ -/// SPDX-FileCopyrightText: 2024 Aravinth Manivannan +// SPDX-FileCopyrightText: 2024 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later + use std::sync::Arc; use derive_builder::Builder;