Skip to main content

haste_repository/pg/
tenant.rs

1use crate::{
2    admin::TenantModelAdmin,
3    pg::{PGConnection, StoreError},
4    types::tenant::{CreateTenant, Tenant, TenantSearchClaims},
5    utilities::{generate_id, validate_id},
6};
7use haste_fhir_operation_error::OperationOutcomeError;
8use haste_jwt::TenantId;
9use sqlx::{PgExecutor, QueryBuilder};
10
11async fn create_tenant<'a, 'e, E>(
12    executor: E,
13    tenant: CreateTenant,
14) -> Result<Tenant, OperationOutcomeError>
15where
16    E: PgExecutor<'e>,
17{
18    let id = tenant
19        .id
20        .unwrap_or_else(|| TenantId::new(generate_id(None)));
21    validate_id(id.as_ref())?;
22
23    let result = sqlx::query_as::<_, Tenant>(
24        r"
25            INSERT INTO tenants (id, subscription_tier)
26            VALUES ($1, $2)
27            RETURNING id, subscription_tier
28        ",
29    )
30    .bind(id)
31    .bind(
32        tenant
33            .subscription_tier
34            .unwrap_or_else(|| "free".to_string()),
35    )
36    .fetch_one(executor)
37    .await;
38
39    match result {
40        Ok(tenant) => Ok(tenant),
41        Err(e) => {
42            if let sqlx::Error::Database(db_error) = &e
43                && db_error.code().as_deref() == Some("23505")
44            {
45                println!("Duplicate tenant ID detected");
46                Err(StoreError::Duplicate.into())
47            } else {
48                Err(StoreError::SQLXError(e).into())
49            }
50        }
51    }
52}
53
54async fn read_tenant<'a, 'e, E>(
55    executor: E,
56    id: &'a str,
57) -> Result<Option<Tenant>, OperationOutcomeError>
58where
59    E: PgExecutor<'e>,
60{
61    let tenant = sqlx::query_as::<_, Tenant>(
62        r"
63            SELECT id, subscription_tier
64            FROM tenants
65            WHERE id = $1
66        ",
67    )
68    .bind(id)
69    .fetch_optional(executor)
70    .await
71    .map_err(StoreError::SQLXError)?;
72
73    Ok(tenant)
74}
75
76async fn update_tenant<'a, 'e, E>(
77    executor: E,
78    tenant: Tenant,
79) -> Result<Tenant, OperationOutcomeError>
80where
81    E: PgExecutor<'e>,
82{
83    let updated_tenant = sqlx::query_as::<_, Tenant>(
84        r"
85            UPDATE tenants
86            SET subscription_tier = $1
87            WHERE id = $2
88            RETURNING id, subscription_tier
89        ",
90    )
91    .bind(tenant.subscription_tier)
92    .bind(tenant.id)
93    .fetch_one(executor)
94    .await
95    .map_err(StoreError::SQLXError)?;
96
97    Ok(updated_tenant)
98}
99
100async fn delete_tenant<'a, 'e, E>(executor: E, id: &'a str) -> Result<(), OperationOutcomeError>
101where
102    E: PgExecutor<'e>,
103{
104    sqlx::query(
105        r"
106            DELETE FROM tenants
107            WHERE id = $1
108        ",
109    )
110    .bind(id)
111    .execute(executor)
112    .await
113    .map_err(StoreError::SQLXError)?;
114
115    Ok(())
116}
117
118async fn search_tenant<'a, 'e, E>(
119    executor: E,
120    clauses: &'a TenantSearchClaims,
121) -> Result<Vec<Tenant>, OperationOutcomeError>
122where
123    E: PgExecutor<'e>,
124{
125    let mut query_builder: QueryBuilder<sqlx::Postgres> =
126        QueryBuilder::new(r"SELECT id, subscription_tier FROM tenants WHERE ");
127
128    if let Some(subscription_tier) = clauses.subscription_tier.as_ref() {
129        query_builder
130            .push(" subscription_tier = ")
131            .push_bind(subscription_tier);
132    }
133
134    let query = query_builder.build_query_as::<Tenant>();
135
136    let tenants: Vec<Tenant> = query.fetch_all(executor).await.map_err(StoreError::from)?;
137
138    Ok(tenants)
139}
140
141impl<Key: AsRef<str> + Send + Sync>
142    TenantModelAdmin<CreateTenant, Tenant, TenantSearchClaims, Tenant, Key> for PGConnection
143{
144    async fn create(
145        &self,
146        _tenant: &TenantId,
147        new_tenant: CreateTenant,
148    ) -> Result<Tenant, OperationOutcomeError> {
149        match self {
150            PGConnection::Pool(pool, _) => create_tenant(pool, new_tenant).await,
151            PGConnection::Transaction(tx, _) => {
152                let mut tx = tx.lock().await;
153                create_tenant(&mut **tx, new_tenant).await
154            }
155        }
156    }
157
158    async fn read(
159        &self,
160        _tenant: &TenantId,
161        id: &Key,
162    ) -> Result<Option<Tenant>, haste_fhir_operation_error::OperationOutcomeError> {
163        match self {
164            PGConnection::Pool(pool, _) => read_tenant(pool, id.as_ref()).await,
165            PGConnection::Transaction(tx, _) => {
166                let mut tx = tx.lock().await;
167                read_tenant(&mut **tx, id.as_ref()).await
168            }
169        }
170    }
171
172    async fn update(
173        &self,
174        _tenant: &TenantId,
175        model: Tenant,
176    ) -> Result<Tenant, haste_fhir_operation_error::OperationOutcomeError> {
177        match self {
178            PGConnection::Pool(pool, _) => update_tenant(pool, model).await,
179            PGConnection::Transaction(tx, _) => {
180                let mut tx = tx.lock().await;
181                update_tenant(&mut **tx, model).await
182            }
183        }
184    }
185
186    async fn delete(
187        &self,
188        _tenant: &TenantId,
189        id: &Key,
190    ) -> Result<(), haste_fhir_operation_error::OperationOutcomeError> {
191        match self {
192            PGConnection::Pool(pool, _) => delete_tenant(pool, id.as_ref()).await,
193            PGConnection::Transaction(tx, _) => {
194                let mut tx = tx.lock().await;
195                delete_tenant(&mut **tx, id.as_ref()).await
196            }
197        }
198    }
199
200    async fn search(
201        &self,
202        _tenant: &TenantId,
203        claims: &TenantSearchClaims,
204    ) -> Result<Vec<Tenant>, OperationOutcomeError> {
205        match self {
206            PGConnection::Pool(pool, _) => search_tenant(pool, claims).await,
207            PGConnection::Transaction(tx, _) => {
208                let mut tx = tx.lock().await;
209                search_tenant(&mut **tx, claims).await
210            }
211        }
212    }
213}