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