#![recursion_limit = "1024"]
#![allow(unused_imports)]
pub mod domain;
pub mod infrastructure;
pub mod application;
pub mod presentation;
pub mod seeders;
pub mod handlers; pub mod routes; pub mod exports; use domain::entity::*;
use infrastructure::persistence::*;
pub use application::service::AttributeService;
pub use application::service::AttributeValueService;
pub use application::service::BrandService;
pub use application::service::ItemService;
pub use application::service::ItemGroupService;
pub use application::service::ItemVariantService;
pub use application::service::UomService;
pub use application::service::UomConversionService;
pub use application::service::{
CatalogWriteError, CatalogWriteService, NewAttribute, NewAttributeValue, NewBrand, NewItem,
NewItemGroup, NewItemVariant, NewUom,
};
pub use domain::services::uom_tree::{
convert_quantity, ConversionRounding, UomChain, UomChainNode, UomConversionError,
};
pub use presentation::http::create_guarded_catalog_routes;
use std::sync::Arc;
use axum::Router;
use sqlx::PgPool;
pub async fn assert_rls_enforced(pool: &PgPool) -> anyhow::Result<()> {
let is_super: bool = sqlx::query_scalar("SELECT current_setting('is_superuser')::boolean")
.fetch_one(pool)
.await?;
if is_super {
anyhow::bail!(
"backbone-catalog RLS guard: the database connection is a SUPERUSER, which bypasses \
ROW LEVEL SECURITY and defeats the composing host's org fence. Connect the app as \
a non-superuser role (migrations/seeders may still run as the owner)."
);
}
Ok(())
}
pub struct CatalogModule {
pub(crate) attribute_service: Arc<AttributeService>,
pub(crate) attribute_value_service: Arc<AttributeValueService>,
pub(crate) brand_service: Arc<BrandService>,
pub(crate) item_service: Arc<ItemService>,
pub(crate) item_group_service: Arc<ItemGroupService>,
pub(crate) item_variant_service: Arc<ItemVariantService>,
pub(crate) uom_service: Arc<UomService>,
pub(crate) uom_conversion_service: Arc<UomConversionService>,
pub(crate) catalog_write_service: Arc<CatalogWriteService>,
}
impl CatalogModule {
pub fn builder() -> CatalogModuleBuilder {
CatalogModuleBuilder::new()
}
#[cfg(any(test, feature = "unguarded"))]
pub fn all_crud_routes(&self) -> Router {
use presentation::http::{
create_attribute_routes,
create_attribute_value_routes,
create_brand_routes,
create_item_routes,
create_item_group_routes,
create_item_variant_routes,
create_uom_routes,
create_uom_conversion_routes,
};
Router::new()
.merge(create_attribute_routes(self.attribute_service.clone()))
.merge(create_attribute_value_routes(self.attribute_value_service.clone()))
.merge(create_brand_routes(self.brand_service.clone()))
.merge(create_item_routes(self.item_service.clone()))
.merge(create_item_group_routes(self.item_group_service.clone()))
.merge(create_item_variant_routes(self.item_variant_service.clone()))
.merge(create_uom_routes(self.uom_service.clone()))
.merge(create_uom_conversion_routes(self.uom_conversion_service.clone()))
}
#[cfg(any(test, feature = "unguarded"))]
#[deprecated(note = "mounts unvalidated generic CRUD on every entity; compose a guarded router for production, or call all_crud_routes() for the intentional full/unguarded surface")]
pub fn routes(&self) -> Router {
self.all_crud_routes()
}
}
pub struct CatalogModuleBuilder {
db_pool: Option<PgPool>,
}
impl CatalogModuleBuilder {
pub fn new() -> Self {
Self {
db_pool: None,
}
}
pub fn with_database(mut self, pool: PgPool) -> Self {
self.db_pool = Some(pool);
self
}
pub fn build(self) -> anyhow::Result<CatalogModule> {
let db_pool = self.db_pool
.ok_or_else(|| anyhow::anyhow!("Database pool not configured"))?;
let attribute_repository = Arc::new(AttributeRepository::new(db_pool.clone()));
let attribute_service = Arc::new(AttributeService::with_repository(attribute_repository.clone()));
let attribute_value_repository = Arc::new(AttributeValueRepository::new(db_pool.clone()));
let attribute_value_service = Arc::new(AttributeValueService::with_repository(attribute_value_repository.clone()));
let brand_repository = Arc::new(BrandRepository::new(db_pool.clone()));
let brand_service = Arc::new(BrandService::with_repository(brand_repository.clone()));
let item_repository = Arc::new(ItemRepository::new(db_pool.clone()));
let item_service = Arc::new(ItemService::with_repository(item_repository.clone()));
let item_group_repository = Arc::new(ItemGroupRepository::new(db_pool.clone()));
let item_group_service = Arc::new(ItemGroupService::with_repository(item_group_repository.clone()));
let item_variant_repository = Arc::new(ItemVariantRepository::new(db_pool.clone()));
let item_variant_service = Arc::new(ItemVariantService::with_repository(item_variant_repository.clone()));
let uom_repository = Arc::new(UomRepository::new(db_pool.clone()));
let uom_service = Arc::new(UomService::with_repository(uom_repository.clone()));
let uom_conversion_repository = Arc::new(UomConversionRepository::new(db_pool.clone()));
let uom_conversion_service = Arc::new(UomConversionService::with_repository(uom_conversion_repository.clone()));
let catalog_write_service = Arc::new(CatalogWriteService::new(db_pool.clone()));
Ok(CatalogModule {
attribute_service,
attribute_value_service,
brand_service,
item_service,
item_group_service,
item_variant_service,
uom_service,
uom_conversion_service,
catalog_write_service,
})
}
}
impl Default for CatalogModuleBuilder {
fn default() -> Self {
Self::new()
}
}