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}