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