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-FileCopyrightText: 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::InventoryDBPostgresAdapter;
|
use super::InventoryDBPostgresAdapter;
|
||||||
|
|
|
@ -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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::InventoryDBPostgresAdapter;
|
use super::InventoryDBPostgresAdapter;
|
||||||
|
|
|
@ -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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::InventoryDBPostgresAdapter;
|
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_name_exists_for_product;
|
||||||
mod customization_view;
|
mod customization_view;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod get_category;
|
||||||
mod product_id_exists;
|
mod product_id_exists;
|
||||||
mod product_name_exists_for_category;
|
mod product_name_exists_for_category;
|
||||||
mod product_view;
|
mod product_view;
|
||||||
|
|
|
@ -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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::InventoryDBPostgresAdapter;
|
use super::InventoryDBPostgresAdapter;
|
||||||
|
|
|
@ -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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::InventoryDBPostgresAdapter;
|
use super::InventoryDBPostgresAdapter;
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
|
mod full_text_search;
|
||||||
|
|
|
@ -18,4 +18,8 @@ pub enum InventoryDBError {
|
||||||
DuplicateCustomizationID,
|
DuplicateCustomizationID,
|
||||||
DuplicateCustomizationName,
|
DuplicateCustomizationName,
|
||||||
InternalError,
|
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_id_exists;
|
||||||
pub mod customization_name_exists_for_product;
|
pub mod customization_name_exists_for_product;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
pub mod get_category;
|
||||||
pub mod product_id_exists;
|
pub mod product_id_exists;
|
||||||
pub mod product_name_exists_for_category;
|
pub mod product_name_exists_for_category;
|
||||||
pub mod store_id_exists;
|
pub mod store_id_exists;
|
||||||
|
|
|
@ -10,8 +10,12 @@ use mockall::*;
|
||||||
|
|
||||||
use super::errors::*;
|
use super::errors::*;
|
||||||
use crate::inventory::{
|
use crate::inventory::{
|
||||||
application::port::output::db::{
|
application::port::output::{
|
||||||
category_id_exists::*, product_id_exists::*, product_name_exists_for_category::*,
|
db::{
|
||||||
|
category_id_exists::*, get_category::*, product_id_exists::*,
|
||||||
|
product_name_exists_for_category::*,
|
||||||
|
},
|
||||||
|
full_text_search::add_product_to_store::*,
|
||||||
},
|
},
|
||||||
domain::{
|
domain::{
|
||||||
add_product_command::AddProductCommand,
|
add_product_command::AddProductCommand,
|
||||||
|
@ -34,6 +38,8 @@ pub struct AddProductService {
|
||||||
db_category_id_exists: CategoryIDExistsDBPortObj,
|
db_category_id_exists: CategoryIDExistsDBPortObj,
|
||||||
db_product_name_exists_for_category: ProductNameExistsForCategoryDBPortObj,
|
db_product_name_exists_for_category: ProductNameExistsForCategoryDBPortObj,
|
||||||
db_product_id_exists: ProductIDExistsDBPortObj,
|
db_product_id_exists: ProductIDExistsDBPortObj,
|
||||||
|
db_get_category: GetCategoryDBPortObj,
|
||||||
|
fts_add_product: AddProductToStoreFTSPortObj,
|
||||||
get_uuid: GetUUIDInterfaceObj,
|
get_uuid: GetUUIDInterfaceObj,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +89,14 @@ impl AddProductUseCase for AddProductService {
|
||||||
return Err(InventoryError::DuplicateProductName);
|
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()
|
Ok(ProductAddedEventBuilder::default()
|
||||||
.added_by_user(*cmd.adding_by())
|
.added_by_user(*cmd.adding_by())
|
||||||
.name(product.name().into())
|
.name(product.name().into())
|
||||||
|
@ -148,7 +162,9 @@ pub mod tests {
|
||||||
mock_product_name_exists_for_category_db_port_false(IS_CALLED_ONLY_ONCE),
|
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_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))
|
.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))
|
.get_uuid(mock_get_uuid(IS_CALLED_ONLY_ONCE))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -176,6 +192,8 @@ pub mod tests {
|
||||||
.db_category_id_exists(mock_category_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
.db_category_id_exists(mock_category_id_exists_db_port_true(IS_CALLED_ONLY_ONCE))
|
||||||
.get_uuid(mock_get_uuid(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_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()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -195,6 +213,8 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
.db_product_id_exists(mock_product_id_exists_db_port_false(IS_NEVER_CALLED))
|
.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_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))
|
.get_uuid(mock_get_uuid(IS_NEVER_CALLED))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -6,7 +6,9 @@ use derive_more::{Display, Error};
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde::{Deserialize, Serialize};
|
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>;
|
pub type InventoryResult<V> = Result<V, InventoryError>;
|
||||||
|
|
||||||
|
@ -47,6 +49,17 @@ impl From<InventoryDBError> for InventoryError {
|
||||||
Self::InternalError
|
Self::InternalError
|
||||||
}
|
}
|
||||||
InventoryDBError::InternalError => 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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use derive_builder::Builder;
|
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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use derive_builder::Builder;
|
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
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
|
Loading…
Reference in a new issue