haste_repository/pg/
membership.rs

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}