feat: get Category from DB and order Product index while Product creation #57
16 changed files with 196 additions and 6 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
98
src/inventory/adapters/output/db/postgres/get_category.rs
Normal file
98
src/inventory/adapters/output/db/postgres/get_category.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
mod db;
|
||||
mod full_text_search;
|
||||
|
|
|
@ -18,4 +18,8 @@ pub enum InventoryDBError {
|
|||
DuplicateCustomizationID,
|
||||
DuplicateCustomizationName,
|
||||
InternalError,
|
||||
ProductIDNotFound,
|
||||
CategoryIDNotFound,
|
||||
CustomizationIDNotFound,
|
||||
StoreIDNotFound,
|
||||
}
|
||||
|
|
44
src/inventory/application/port/output/db/get_category.rs
Normal file
44
src/inventory/application/port/output/db/get_category.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue