haste_repository/pg/
user.rs

1use crate::{
2    admin::{Login, TenantAuthAdmin},
3    pg::{PGConnection, StoreError},
4    types::user::{
5        AuthMethod, CreateUser, LoginMethod, LoginResult, UpdateUser, User, UserRole,
6        UserSearchClauses,
7    },
8};
9use haste_fhir_operation_error::OperationOutcomeError;
10use haste_jwt::TenantId;
11use sqlx::{Acquire, Postgres, QueryBuilder};
12
13fn login<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
14    connection: Connection,
15    tenant: &'a TenantId,
16    method: &'a LoginMethod,
17) -> impl Future<Output = Result<LoginResult, OperationOutcomeError>> + Send + 'a {
18    async move {
19        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
20        match method {
21            LoginMethod::EmailPassword { email, password } => {
22                let user = sqlx::query_as!(
23                    User,
24                    r#"
25                  SELECT id, tenant as "tenant: TenantId", email, role as "role: UserRole", method as "method: AuthMethod", provider_id FROM users WHERE tenant = $1 AND method = $2 AND email = $3 AND password = crypt($4, password)
26                "#,
27                    tenant.as_ref(),
28                    AuthMethod::EmailPassword as AuthMethod,
29                    email,
30                    password
31                ).fetch_optional(&mut *conn).await.map_err(StoreError::from)?;
32
33                if let Some(user) = user {
34                    Ok(LoginResult::Success { user })
35                } else {
36                    Ok(LoginResult::Failure)
37                }
38            }
39            LoginMethod::OIDC {
40                email: _,
41                provider_id: _,
42            } => {
43                todo!();
44            }
45        }
46    }
47}
48
49impl Login for PGConnection {
50    async fn login(
51        &self,
52        tenant: &TenantId,
53        method: &LoginMethod,
54    ) -> Result<LoginResult, haste_fhir_operation_error::OperationOutcomeError> {
55        match &self {
56            PGConnection::Pool(pool, _) => {
57                let res = login(pool, tenant, method).await?;
58                Ok(res)
59            }
60            PGConnection::Transaction(tx, _) => {
61                let mut tx = tx.lock().await;
62
63                let res = login(&mut *tx, tenant, method).await?;
64                Ok(res)
65            }
66        }
67    }
68}
69
70fn create_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
71    connection: Connection,
72    tenant: &'a TenantId,
73    new_user: CreateUser,
74) -> impl Future<Output = Result<User, OperationOutcomeError>> + Send + 'a {
75    async move {
76        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
77
78        let mut query_builder = QueryBuilder::new(
79            r#"
80                INSERT INTO users(tenant, id, email, role, method, provider_id, password)
81            "#,
82        );
83
84        query_builder.push(" VALUES (");
85
86        let mut seperator = query_builder.separated(", ");
87
88        seperator
89            .push_bind(tenant.as_ref())
90            .push_bind(new_user.id)
91            .push_bind(new_user.email)
92            .push_bind(new_user.role as UserRole)
93            .push_bind(new_user.method as AuthMethod);
94
95        if let Some(provider_id) = new_user.provider_id {
96            seperator.push_bind(provider_id);
97        } else {
98            seperator.push_bind(None::<String>);
99        }
100
101        if let Some(password) = new_user.password {
102            seperator
103                .push("crypt(")
104                .push_bind_unseparated(password)
105                .push_unseparated(", gen_salt('bf'))");
106        } else {
107            seperator.push_bind(None::<String>);
108        }
109
110        query_builder.push(r#") RETURNING id, tenant, provider_id, email, role , method"#);
111
112        let query = query_builder.build_query_as();
113
114        let user = query
115            .fetch_one(&mut *conn)
116            .await
117            .map_err(StoreError::SQLXError)?;
118
119        Ok(user)
120    }
121}
122
123fn read_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
124    connection: Connection,
125    tenant: &'a TenantId,
126    id: &'a str,
127) -> impl Future<Output = Result<Option<User>, OperationOutcomeError>> + Send + 'a {
128    async move {
129        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
130        let user = sqlx::query_as!(
131            User,
132            r#"
133                SELECT id, tenant as "tenant: TenantId", provider_id, email, role as "role: UserRole", method as "method: AuthMethod"
134                FROM users
135                WHERE tenant = $1 AND id = $2
136            "#,
137            tenant.as_ref(),
138            id
139        ).fetch_optional(&mut *conn).await.map_err(StoreError::SQLXError)?;
140
141        Ok(user)
142    }
143}
144
145fn update_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
146    connection: Connection,
147    tenant: &'a TenantId,
148    model: UpdateUser,
149) -> impl Future<Output = Result<User, OperationOutcomeError>> + Send + 'a {
150    async move {
151        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
152        let mut query_builder = QueryBuilder::new(
153            r#"
154                UPDATE users SET 
155            "#,
156        );
157
158        let mut update_clauses = query_builder.separated(", ");
159
160        if let Some(provider_id) = model.provider_id {
161            update_clauses
162                .push(" provider_id = ")
163                .push_bind_unseparated(provider_id);
164        }
165
166        if let Some(email) = model.email.as_ref() {
167            update_clauses
168                .push(" email = ")
169                .push_bind_unseparated(email);
170        }
171
172        if let Some(role) = model.role.as_ref() {
173            update_clauses.push(" role = ").push_bind_unseparated(role);
174        }
175
176        if let Some(method) = model.method.as_ref() {
177            update_clauses
178                .push(" method = ")
179                .push_bind_unseparated(method);
180        }
181
182        if let Some(password) = model.password {
183            update_clauses
184                .push(" password = crypt(")
185                .push_bind_unseparated(password)
186                .push_unseparated(", gen_salt('bf'))");
187        }
188
189        update_clauses
190            .push(" tenant = ")
191            .push_bind_unseparated(tenant.as_ref());
192
193        query_builder.push(" WHERE id = ");
194        query_builder.push_bind(model.id);
195
196        query_builder.push(r#" RETURNING id, tenant, provider_id, email, role, method"#);
197
198        let query = query_builder.build_query_as();
199
200        let user = query
201            .fetch_one(&mut *conn)
202            .await
203            .map_err(StoreError::SQLXError)?;
204
205        Ok(user)
206    }
207}
208
209fn delete_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
210    connection: Connection,
211    tenant: &'a TenantId,
212    id: &'a str,
213) -> impl Future<Output = Result<(), OperationOutcomeError>> + Send + 'a {
214    async move {
215        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
216        let _user = sqlx::query_as!(
217            User,
218            r#"
219                DELETE FROM users
220                WHERE tenant = $1 AND id = $2
221                RETURNING id, tenant as "tenant: TenantId", provider_id, email, role as "role: UserRole", method as "method: AuthMethod"
222            "#,
223            tenant.as_ref(),
224            id
225        ).fetch_optional(&mut *conn).await.map_err(StoreError::SQLXError)?;
226
227        Ok(())
228    }
229}
230
231fn search_user<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
232    connection: Connection,
233    tenant: &'a TenantId,
234    clauses: &'a UserSearchClauses,
235) -> impl Future<Output = Result<Vec<User>, OperationOutcomeError>> + Send + 'a {
236    async move {
237        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
238        let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
239            r#"SELECT id, tenant, email, role, method, provider_id FROM users WHERE  "#,
240        );
241
242        let mut seperator = query_builder.separated(" AND ");
243        seperator
244            .push(" tenant = ")
245            .push_bind_unseparated(tenant.as_ref());
246
247        if let Some(email) = clauses.email.as_ref() {
248            seperator.push(" email = ").push_bind_unseparated(email);
249        }
250
251        if let Some(role) = clauses.role.as_ref() {
252            seperator.push(" role = ").push_bind_unseparated(role);
253        }
254
255        if let Some(method) = clauses.method.as_ref() {
256            seperator.push(" method = ").push_bind_unseparated(method);
257        }
258
259        let query = query_builder.build_query_as();
260
261        let users: Vec<User> = query
262            .fetch_all(&mut *conn)
263            .await
264            .map_err(StoreError::from)?;
265
266        Ok(users)
267    }
268}
269
270impl<Key: AsRef<str> + Send + Sync>
271    TenantAuthAdmin<CreateUser, User, UserSearchClauses, UpdateUser, Key> for PGConnection
272{
273    async fn create(
274        &self,
275        tenant: &TenantId,
276        new_user: CreateUser,
277    ) -> Result<User, OperationOutcomeError> {
278        match self {
279            PGConnection::Pool(pool, _) => {
280                let res = create_user(pool, tenant, new_user).await?;
281                Ok(res)
282            }
283            PGConnection::Transaction(tx, _) => {
284                let mut tx = tx.lock().await;
285                let res = create_user(&mut *tx, tenant, new_user).await?;
286                Ok(res)
287            }
288        }
289    }
290
291    async fn read(
292        &self,
293        tenant: &TenantId,
294        id: &Key,
295    ) -> Result<Option<User>, OperationOutcomeError> {
296        match self {
297            PGConnection::Pool(pool, _) => {
298                let res = read_user(pool, tenant, id.as_ref()).await?;
299                Ok(res)
300            }
301            PGConnection::Transaction(tx, _) => {
302                let mut tx = tx.lock().await;
303                let res = read_user(&mut *tx, tenant, id.as_ref()).await?;
304                Ok(res)
305            }
306        }
307    }
308
309    async fn update(
310        &self,
311        tenant: &TenantId,
312        user: UpdateUser,
313    ) -> Result<User, OperationOutcomeError> {
314        match self {
315            PGConnection::Pool(pool, _) => {
316                let res = update_user(pool, &tenant, user).await?;
317                Ok(res)
318            }
319            PGConnection::Transaction(tx, _) => {
320                let mut tx = tx.lock().await;
321                let res = update_user(&mut *tx, &tenant, user).await?;
322                Ok(res)
323            }
324        }
325    }
326
327    async fn delete(&self, tenant: &TenantId, id: &Key) -> Result<(), OperationOutcomeError> {
328        match self {
329            PGConnection::Pool(pool, _) => {
330                let res = delete_user(pool, tenant, id.as_ref()).await?;
331                Ok(res)
332            }
333            PGConnection::Transaction(tx, _) => {
334                let mut tx = tx.lock().await;
335                let res = delete_user(&mut *tx, tenant, id.as_ref()).await?;
336                Ok(res)
337            }
338        }
339    }
340
341    async fn search(
342        &self,
343        tenant: &TenantId,
344        clauses: &UserSearchClauses,
345    ) -> Result<Vec<User>, OperationOutcomeError> {
346        match self {
347            PGConnection::Pool(pool, _) => {
348                let res = search_user(pool, tenant, clauses).await?;
349                Ok(res)
350            }
351            PGConnection::Transaction(tx, _) => {
352                let mut tx = tx.lock().await;
353                let res = search_user(&mut *tx, tenant, clauses).await?;
354                Ok(res)
355            }
356        }
357    }
358}