feat: get Category from DB and order Product index while Product creation #57

Merged
realaravinth merged 1 commit from index-product into master 2024-07-17 21:45:47 +05:30
16 changed files with 196 additions and 6 deletions

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use uuid::Uuid;
use super::InventoryDBPostgresAdapter;

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use uuid::Uuid;
use super::InventoryDBPostgresAdapter;

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use uuid::Uuid;
use super::InventoryDBPostgresAdapter;

View file

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// 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<String>,
store_id: Uuid,
category_id: Uuid,
deleted: bool,
}
impl From<InnerCategory> 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<Category> {
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;
}
}

View file

@ -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;

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use uuid::Uuid;
use super::InventoryDBPostgresAdapter;

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use uuid::Uuid;
use super::InventoryDBPostgresAdapter;

View file

@ -3,3 +3,4 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
mod db;
mod full_text_search;

View file

@ -18,4 +18,8 @@ pub enum InventoryDBError {
DuplicateCustomizationID,
DuplicateCustomizationName,
InternalError,
ProductIDNotFound,
CategoryIDNotFound,
CustomizationIDNotFound,
StoreIDNotFound,
}

View file

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// 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<Category>;
}
pub type GetCategoryDBPortObj = std::sync::Arc<dyn GetCategoryDBPort>;
#[cfg(test)]
pub mod tests {
use super::*;
use std::sync::Arc;
pub fn mock_get_category_db_port(times: Option<usize>) -> 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)
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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<V> = Result<V, InventoryError>;
@ -47,6 +49,17 @@ impl From<InventoryDBError> 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<InventoryFTSError> for InventoryError {
fn from(value: InventoryFTSError) -> Self {
error!("{}", value);
InventoryError::InternalError
}
}

View file

@ -1,6 +1,7 @@
/// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::sync::Arc;
use derive_builder::Builder;

View file

@ -1,6 +1,7 @@
/// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::sync::Arc;
use derive_builder::Builder;

View file

@ -1,6 +1,7 @@
/// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
// SPDX-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::sync::Arc;
use derive_builder::Builder;