Skip to main content

haste_repository/pg/
membership.rs

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}