Skip to main content

backbone_support/infrastructure/persistence/
service_level_agreement_repository.rs

1//! Repository for ServiceLevelAgreement 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 SLA SQL (4-layer rule: services orchestrate, repositories hold the SQL).
6//!
7//! Tenancy (ADR-0029): the SQL here carries no tenant key. The scoped-execute helpers ride the
8//! request-dedicated connection when the composing service bound one (its fence variables govern
9//! what the RLS layer accepts) and fall back to a plain pool execute otherwise.
10//!
11//! Thin newtype over `backbone_orm::GenericCrudRepository<ServiceLevelAgreement, backbone_orm::SoftDelete>`.
12//! All standard CRUD methods are available via `Deref`.
13
14use anyhow::Result;
15use sqlx::{PgPool, Row};
16use uuid::Uuid;
17
18use backbone_orm::org_scope;
19
20use crate::domain::entity::ServiceLevelAgreement;
21
22/// Table name for ServiceLevelAgreement entities
23pub const TABLE_NAME: &str = "support.service_level_agreements";
24
25/// Repository for ServiceLevelAgreement entities.
26///
27/// All standard CRUD, soft-delete, pagination, and bulk methods are
28/// provided automatically via `Deref` to `backbone_orm::GenericCrudRepository`.
29pub struct ServiceLevelAgreementRepository(
30    backbone_orm::GenericCrudRepository<ServiceLevelAgreement, backbone_orm::SoftDelete>,
31);
32
33impl std::ops::Deref for ServiceLevelAgreementRepository {
34    type Target =
35        backbone_orm::GenericCrudRepository<ServiceLevelAgreement, backbone_orm::SoftDelete>;
36    fn deref(&self) -> &Self::Target {
37        &self.0
38    }
39}
40
41impl ServiceLevelAgreementRepository {
42    /// Create a new repository instance.
43    pub fn new(pool: PgPool) -> Self {
44        Self(backbone_orm::GenericCrudRepository::new(pool, TABLE_NAME))
45    }
46}
47
48/// The exact row an SLA definition writes.
49///
50/// Mirrors the raw column shape rather than the `ServiceLevelAgreement` entity — the write service
51/// composes it from its `NewSla` DTO and never hands a generated entity to the SQL.
52pub struct NewSlaRow<'a> {
53    pub id: Uuid,
54    pub name: &'a str,
55    pub is_default: bool,
56}
57
58/// Hand-written ServiceLevelAgreement SQL. Lives here (not in the write service) per the module's
59/// 4-layer rule: services orchestrate and own the unit of work, repositories hold the SQL.
60impl ServiceLevelAgreementRepository {
61    /// Insert the SLA header. Takes the CALLER'S connection so the header and its per-priority target
62    /// rows commit as one unit. The caller has already relayed the ambient org scope onto that
63    /// connection — don't re-bind here.
64    pub async fn insert_sla(
65        &self,
66        conn: &mut sqlx::PgConnection,
67        s: &NewSlaRow<'_>,
68    ) -> Result<(), sqlx::Error> {
69        sqlx::query(
70            r#"INSERT INTO support.service_level_agreements (id, name, is_default, status)
71               VALUES ($1,$2,$3,'active')"#,
72        )
73        .bind(s.id)
74        .bind(s.name)
75        .bind(s.is_default)
76        .execute(conn)
77        .await?;
78        Ok(())
79    }
80
81    /// The default active SLA, if one exists — the fallback when a ticket is raised without an
82    /// explicit SLA. Under a composing service's row fence (RLS), the scope bound on the request
83    /// connection limits what is visible; with no fence mounted, this is the deployment's single
84    /// default.
85    pub async fn find_default_id(&self, pool: &PgPool) -> Result<Option<Uuid>, sqlx::Error> {
86        let row = org_scope::fetch_optional_row_scoped(
87            pool,
88            sqlx::query(
89                r#"SELECT id FROM support.service_level_agreements
90                   WHERE is_default=true AND status='active'
91                     AND (metadata->>'deleted_at') IS NULL LIMIT 1"#,
92            ),
93        )
94        .await?;
95        Ok(row.map(|r| r.get::<Uuid, _>("id")))
96    }
97}
98
99backbone_core::impl_crud_repository!(
100    ServiceLevelAgreementRepository,
101    ServiceLevelAgreement,
102    soft_delete
103);