1use crate::{
2 admin::ProjectModelAdmin,
3 pg::{PGConnection, StoreError},
4 types::scope::{CreateScope, Scope, ScopeKey, ScopeSearchClaims, UpdateScope},
5};
6use haste_fhir_operation_error::OperationOutcomeError;
7use haste_jwt::{ProjectId, TenantId};
8use sqlx::{PgExecutor, QueryBuilder};
9
10async fn create_scope<'a, 'e, E>(
11 executor: E,
12 tenant: &'a TenantId,
13 project: &'a ProjectId,
14 scope: CreateScope,
15) -> Result<Scope, OperationOutcomeError>
16where
17 E: PgExecutor<'e>,
18{
19 let scope = sqlx::query_as::<_, Scope>(
20 r"
21 INSERT INTO authorization_scopes(tenant, project, client, user_, scope)
22 VALUES ($1, $2, $3, $4, $5)
23 ON CONFLICT (tenant, project, client, user_)
24 DO UPDATE SET scope = $5
25 RETURNING client, user_, scope, created_at
26 ",
27 )
28 .bind(tenant.as_ref())
29 .bind(project.as_ref())
30 .bind(scope.client.as_ref())
31 .bind(scope.user_.as_ref())
32 .bind(scope.scope)
33 .fetch_one(executor)
34 .await
35 .map_err(StoreError::SQLXError)?;
36
37 Ok(scope)
38}
39
40async fn update_scope<'a, 'e, E>(
41 executor: E,
42 tenant: &'a TenantId,
43 project: &'a ProjectId,
44 model: UpdateScope,
45) -> Result<Scope, OperationOutcomeError>
46where
47 E: PgExecutor<'e>,
48{
49 let mut query_builder = QueryBuilder::new(
50 r"
51 UPDATE authorization_scopes SET
52 ",
53 );
54
55 let mut set_statements = query_builder.separated(", ");
56
57 set_statements
58 .push(" scope = ")
59 .push_bind_unseparated(model.scope);
60
61 query_builder.push(" WHERE ");
62
63 let mut where_statements = query_builder.separated(" AND ");
64 where_statements
65 .push(" tenant = ")
66 .push_bind_unseparated(tenant.as_ref())
67 .push(" project = ")
68 .push_bind_unseparated(project.as_ref())
69 .push(" client = ")
70 .push_bind_unseparated(model.client.as_ref())
71 .push(" user_ = ")
72 .push_bind_unseparated(model.user_.as_ref());
73
74 query_builder.push(r" RETURNING client, user_, scope, created_at");
75
76 let query = query_builder.build_query_as::<Scope>();
77
78 let scope = query
79 .fetch_one(executor)
80 .await
81 .map_err(StoreError::SQLXError)?;
82
83 Ok(scope)
84}
85
86async fn read_scope<'a, 'e, E>(
87 executor: E,
88 tenant: &'a TenantId,
89 project: &'a ProjectId,
90 id: &'a ScopeKey,
91) -> Result<Option<Scope>, OperationOutcomeError>
92where
93 E: PgExecutor<'e>,
94{
95 let scope = sqlx::query_as::<_, Scope>(
96 r"
97 SELECT user_, client, scope, created_at
98 FROM authorization_scopes
99 WHERE tenant = $1 AND project = $2 AND client = $3 AND user_ = $4
100 ",
101 )
102 .bind(tenant.as_ref())
103 .bind(project.as_ref())
104 .bind(String::from(id.0.clone()))
105 .bind(String::from(id.1.clone()))
106 .fetch_optional(executor)
107 .await
108 .map_err(StoreError::SQLXError)?;
109
110 Ok(scope)
111}
112
113async fn delete_scope<'a, 'e, E>(
114 executor: E,
115 tenant: &'a TenantId,
116 project: &'a ProjectId,
117 key: &'a ScopeKey,
118) -> Result<(), OperationOutcomeError>
119where
120 E: PgExecutor<'e>,
121{
122 sqlx::query(
123 r"
124 DELETE FROM authorization_scopes
125 WHERE tenant = $1 AND project = $2 AND client = $3 AND user_ = $4
126 ",
127 )
128 .bind(tenant.as_ref())
129 .bind(project.as_ref())
130 .bind(key.0.as_ref())
131 .bind(key.1.as_ref())
132 .execute(executor)
133 .await
134 .map_err(StoreError::SQLXError)?;
135
136 Ok(())
137}
138
139async fn search_scopes<'a, 'e, E>(
140 executor: E,
141 tenant: &'a TenantId,
142 project: &'a ProjectId,
143 clauses: &'a ScopeSearchClaims,
144) -> Result<Vec<Scope>, OperationOutcomeError>
145where
146 E: PgExecutor<'e>,
147{
148 let mut query_builder: QueryBuilder<sqlx::Postgres> = QueryBuilder::new(
149 r"SELECT user_, client, scope, created_at FROM authorization_scopes WHERE ",
150 );
151
152 let mut seperator = query_builder.separated(" AND ");
153 seperator
154 .push(" tenant = ")
155 .push_bind_unseparated(tenant.as_ref())
156 .push(" project = ")
157 .push_bind_unseparated(project.as_ref());
158
159 if let Some(user_id) = clauses.user_.as_ref() {
160 seperator
161 .push(" user_ = ")
162 .push_bind_unseparated(user_id.as_ref());
163 }
164
165 if let Some(client) = clauses.client.as_ref() {
166 seperator
167 .push(" client = ")
168 .push_bind_unseparated(client.as_ref());
169 }
170
171 let query = query_builder.build_query_as::<Scope>();
172
173 let scopes: Vec<Scope> = query.fetch_all(executor).await.map_err(StoreError::from)?;
174
175 Ok(scopes)
176}
177
178impl ProjectModelAdmin<CreateScope, Scope, ScopeSearchClaims, UpdateScope, ScopeKey>
179 for PGConnection
180{
181 async fn create(
182 &self,
183 tenant: &TenantId,
184 project: &ProjectId,
185 new_scope: CreateScope,
186 ) -> Result<Scope, OperationOutcomeError> {
187 match self {
188 PGConnection::Pool(pool, _) => create_scope(pool, tenant, project, new_scope).await,
189 PGConnection::Transaction(tx, _) => {
190 let mut tx = tx.lock().await;
191 create_scope(&mut **tx, tenant, project, new_scope).await
192 }
193 }
194 }
195
196 async fn read(
197 &self,
198 tenant: &TenantId,
199 project: &ProjectId,
200 key: &ScopeKey,
201 ) -> Result<Option<Scope>, OperationOutcomeError> {
202 match self {
203 PGConnection::Pool(pool, _) => read_scope(pool, tenant, project, key).await,
204 PGConnection::Transaction(tx, _) => {
205 let mut tx = tx.lock().await;
206 read_scope(&mut **tx, tenant, project, key).await
207 }
208 }
209 }
210
211 async fn update(
212 &self,
213 tenant: &TenantId,
214 project: &ProjectId,
215 model: UpdateScope,
216 ) -> Result<Scope, OperationOutcomeError> {
217 match self {
218 PGConnection::Pool(pool, _) => update_scope(pool, tenant, project, model).await,
219 PGConnection::Transaction(tx, _) => {
220 let mut tx = tx.lock().await;
221 update_scope(&mut **tx, tenant, project, model).await
222 }
223 }
224 }
225
226 async fn delete(
227 &self,
228 tenant: &TenantId,
229 project: &ProjectId,
230 key: &ScopeKey,
231 ) -> Result<(), OperationOutcomeError> {
232 match self {
233 PGConnection::Pool(pool, _) => delete_scope(pool, tenant, project, key).await,
234 PGConnection::Transaction(tx, _) => {
235 let mut tx = tx.lock().await;
236 delete_scope(&mut **tx, tenant, project, key).await
237 }
238 }
239 }
240
241 async fn search(
242 &self,
243 tenant: &TenantId,
244 project: &ProjectId,
245 clauses: &ScopeSearchClaims,
246 ) -> Result<Vec<Scope>, OperationOutcomeError> {
247 match self {
248 PGConnection::Pool(pool, _) => search_scopes(pool, tenant, project, clauses).await,
249 PGConnection::Transaction(tx, _) => {
250 let mut tx = tx.lock().await;
251 search_scopes(&mut **tx, tenant, project, clauses).await
252 }
253 }
254 }
255}