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);