backbone_catalog/infrastructure/persistence/item_group_repository.rs
1//! Repository for ItemGroup entities
2//!
3//! Originally generated by metaphor-schema; now **user-owned** — this exact path is declared under
4//! `user_owned` in `metaphor.codegen.yaml`, so the generator skips it wholesale. The custom methods
5//! below hold the catalog write service's item-group SQL (4-layer rule: services orchestrate, repos
6//! hold SQL).
7//!
8//! Tenant-agnostic (ADR-0029): no statement here names a tenancy column. When the composing
9//! host mounts a request-scoped org fence, plain pool reads ride the request-dedicated
10//! scoped connection and see only in-scope rows; undecorated, they see the whole table.
11//!
12//! Thin newtype over `backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>`.
13//! All standard CRUD methods are available via `Deref`.
14
15use sqlx::PgPool;
16use uuid::Uuid;
17
18use crate::domain::entity::ItemGroup;
19
20/// Table name for ItemGroup entities
21pub const TABLE_NAME: &str = "catalog.item_groups";
22
23/// Repository for ItemGroup entities.
24///
25/// All standard CRUD, soft-delete, pagination, and bulk methods are
26/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
27pub struct ItemGroupRepository(
28 backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>,
29);
30
31impl std::ops::Deref for ItemGroupRepository {
32 type Target = backbone_orm::GenericCrudRepository<ItemGroup, backbone_orm::SoftDelete>;
33 fn deref(&self) -> &Self::Target { &self.0 }
34}
35
36impl ItemGroupRepository {
37 /// Create a new repository instance.
38 pub fn new(pool: PgPool) -> Self {
39 Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
40 }
41}
42
43/// The exact row a validated item-group insert writes.
44pub struct NewItemGroupRow<'a> {
45 pub id: Uuid,
46 pub code: &'a str,
47 pub name: &'a str,
48 pub parent_id: Option<Uuid>,
49 pub is_group: bool,
50}
51
52/// Catalog item-group SQL. Lives here (not in the service) per the module's 4-layer rule.
53impl ItemGroupRepository {
54 /// `EXISTS` probe for a live row (replaces the prior string-built `exists_in` helper
55 /// in the write service). Used for both `parent_id` validation on create-item-group and
56 /// `item_group_id` FK validation on create-item.
57 pub async fn exists_id(&self, executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>, id: Uuid) -> Result<bool, sqlx::Error> {
58 let found: Option<Uuid> = sqlx::query_scalar(
59 "SELECT id FROM catalog.item_groups \
60 WHERE id = $1 AND (metadata->>'deleted_at') IS NULL",
61 )
62 .bind(id)
63 .fetch_optional(executor)
64 .await?;
65 Ok(found.is_some())
66 }
67
68 /// Insert a validated item-group row. Unique-constraint errors propagate as
69 /// `sqlx::Error` so the service can disambiguate code duplicates.
70 pub async fn insert_item_group(
71 &self,
72 executor: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
73 r: &NewItemGroupRow<'_>,
74 ) -> Result<(), sqlx::Error> {
75 sqlx::query(
76 r#"INSERT INTO catalog.item_groups (id, code, name, parent_id, is_group, status)
77 VALUES ($1,$2,$3,$4,$5,'active'::catalog_status)"#,
78 )
79 .bind(r.id)
80 .bind(r.code)
81 .bind(r.name)
82 .bind(r.parent_id)
83 .bind(r.is_group)
84 .execute(executor)
85 .await?;
86 Ok(())
87 }
88}
89
90backbone_core::impl_crud_repository!(ItemGroupRepository, ItemGroup, soft_delete);