Skip to main content

backbone_banking/infrastructure/persistence/
bank_repository.rs

1//! Repository for Bank 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 hand-written Bank SQL (4-layer rule: services orchestrate, repos hold SQL).
6//!
7//! Thin newtype over `backbone_orm::GenericCrudRepository<Bank, backbone_orm::SoftDelete>`.
8//! All standard CRUD methods are available via `Deref`.
9
10use anyhow::Result;
11use sqlx::PgPool;
12use uuid::Uuid;
13
14use backbone_orm::org_scope;
15
16use crate::domain::entity::Bank;
17
18/// Table name for Bank entities
19pub const TABLE_NAME: &str = "banking.banks";
20
21/// Repository for Bank entities.
22///
23/// All standard CRUD, soft-delete, pagination, and bulk methods are
24/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
25pub struct BankRepository(backbone_orm::GenericCrudRepository<Bank, backbone_orm::SoftDelete>);
26
27impl std::ops::Deref for BankRepository {
28    type Target = backbone_orm::GenericCrudRepository<Bank, backbone_orm::SoftDelete>;
29    fn deref(&self) -> &Self::Target {
30        &self.0
31    }
32}
33
34impl BankRepository {
35    /// Create a new repository instance.
36    pub fn new(pool: PgPool) -> Self {
37        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
38    }
39}
40
41/// The exact row a bank registration writes.
42///
43/// `status` is not a field — the INSERT hard-codes `'active'`. `country` is already defaulted by the
44/// caller, so it is not optional here.
45pub struct NewBankRow<'a> {
46    pub id: Uuid,
47    pub name: &'a str,
48    pub swift_bic: Option<&'a str>,
49    pub country: &'a str,
50}
51
52/// Hand-written Bank SQL. Lives here (not in the write service) per the module's 4-layer rule:
53/// services orchestrate and own the unit of work, repositories hold the SQL.
54impl BankRepository {
55    /// Register a bank.
56    ///
57    /// A write outside any transaction: takes the pool and runs `org_scope::execute_scoped`, which
58    /// rides the request-dedicated connection carrying the composing service's org scope when one
59    /// is bound — the decorator-installed row-level fence decides the INSERT's WITH CHECK — and
60    /// executes plainly on the pool otherwise (unfenced by design, ADR-0029). The module invents
61    /// no scope of its own.
62    pub async fn insert_bank(&self, pool: &PgPool, b: &NewBankRow<'_>) -> Result<(), sqlx::Error> {
63        org_scope::execute_scoped(
64            pool,
65            sqlx::query(
66                r#"INSERT INTO banking.banks (id, name, swift_bic, country, status)
67                   VALUES ($1,$2,$3,$4,'active')"#,
68            )
69            .bind(b.id)
70            .bind(b.name)
71            .bind(b.swift_bic)
72            .bind(b.country),
73        )
74        .await?;
75        Ok(())
76    }
77}
78
79backbone_core::impl_crud_repository!(BankRepository, Bank, soft_delete);