Skip to main content

haste_repository/pg/
project.rs

1use crate::{
2    admin::TenantAuthAdmin,
3    pg::{PGConnection, StoreError},
4    types::{
5        SupportedFHIRVersions,
6        project::{CreateProject, Project, ProjectSearchClaims},
7    },
8    utilities::{generate_id, validate_id},
9};
10use haste_fhir_model::r4::generated::terminology::IssueType;
11use haste_fhir_operation_error::OperationOutcomeError;
12use haste_jwt::{ProjectId, TenantId};
13use sqlx::{Acquire, Postgres, QueryBuilder};
14
15fn create_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
16    connection: Connection,
17    tenant: &'a TenantId,
18    project: CreateProject,
19) -> impl Future<Output = Result<Project, OperationOutcomeError>> + Send + 'a {
20    async move {
21        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
22        let id = project.id.unwrap_or(ProjectId::new(generate_id(None)));
23
24        validate_id(id.as_ref())?;
25
26        let project = sqlx::query_as!(
27            Project,
28            r#"INSERT INTO projects (tenant, id, fhir_version, system_created) VALUES ($1, $2, $3, $4) RETURNING tenant as "tenant: TenantId", system_created, id as "id: ProjectId", fhir_version as "fhir_version: SupportedFHIRVersions""#,
29            tenant.as_ref(),
30            id.as_ref(),
31            project.fhir_version as SupportedFHIRVersions,
32            project.system_created
33        )
34        .fetch_one(&mut *conn)
35        .await
36        .map_err(StoreError::SQLXError)?;
37
38        Ok(project)
39    }
40}
41
42fn read_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
43    connection: Connection,
44    tenant: &'a TenantId,
45    id: &'a str,
46) -> impl Future<Output = Result<Option<Project>, OperationOutcomeError>> + Send + 'a {
47    async move {
48        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
49        let project = sqlx::query_as!(
50            Project,
51            r#"SELECT id as "id: ProjectId", tenant as "tenant: TenantId", system_created, fhir_version as "fhir_version: SupportedFHIRVersions" FROM projects where tenant = $1 AND id = $2"#,
52            tenant.as_ref(),    
53            id
54        )
55        .fetch_optional(&mut *conn)
56        .await
57        .map_err(StoreError::SQLXError)?;
58
59        Ok(project)
60    }
61}
62
63fn delete_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
64    connection: Connection,
65    tenant: &'a TenantId,
66    id: &'a str,
67) -> impl Future<Output = Result<(), OperationOutcomeError>> + Send + 'a {
68    async move {
69        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
70        let _deleted_project = sqlx::query_as!(
71            Project,
72            r#"DELETE FROM projects WHERE tenant = $1 AND id = $2 and system_created = false RETURNING id as "id: ProjectId", tenant as "tenant: TenantId", system_created, fhir_version as "fhir_version: SupportedFHIRVersions""#,
73            tenant.as_ref(),
74            id
75        )
76        .fetch_optional(&mut *conn)
77        .await
78        .map_err(|_e| {
79            OperationOutcomeError::error(
80                IssueType::NotFound(None),
81                format!("Project '{}' not found or is system created and cannot be deleted.", id),
82            )
83        })?;
84
85        if !_deleted_project.is_some() {
86            return Err(OperationOutcomeError::error(
87                IssueType::NotFound(None),
88                format!(
89                    "Project '{}' not found or is system created and cannot be deleted.",
90                    id
91                ),
92            ));
93        }
94
95        Ok(())
96    }
97}
98
99fn search_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
100    connection: Connection,
101    tenant: &'a TenantId,
102    clauses: &'a ProjectSearchClaims,
103) -> impl Future<Output = Result<Vec<Project>, OperationOutcomeError>> + Send + 'a {
104    async move {
105        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
106        let mut query_builder: QueryBuilder<Postgres> =
107            QueryBuilder::new(r#"SELECT tenant, id, fhir_version FROM projects WHERE "#);
108
109        let mut and_clauses = query_builder.separated(" AND ");
110
111        and_clauses
112            .push(" tenant = ")
113            .push_bind_unseparated(tenant.as_ref());
114
115        if let Some(id) = clauses.id.as_ref() {
116            and_clauses
117                .push(" id = ")
118                .push_bind_unseparated(id.as_ref());
119        }
120
121        if let Some(fhir_version) = clauses.fhir_version.as_ref() {
122            and_clauses
123                .push(" fhir_version = ")
124                .push_bind_unseparated(fhir_version);
125        }
126
127        if let Some(system_created) = clauses.system_created.as_ref() {
128            and_clauses
129                .push(" system_created = ")
130                .push_bind_unseparated(system_created);
131        }
132
133        let query = query_builder.build_query_as();
134
135        let projects: Vec<Project> = query
136            .fetch_all(&mut *conn)
137            .await
138            .map_err(StoreError::from)?;
139
140        Ok(projects)
141    }
142}
143
144/// Not allowing updates on internal row just reading to confirm it's existance.
145fn update_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
146    connection: Connection,
147    tenant: &'a TenantId,
148    model: Project,
149) -> impl Future<Output = Result<Project, OperationOutcomeError>> + Send + 'a {
150    async move {
151        read_project(connection, tenant, model.id.as_ref())
152            .await?
153            .ok_or_else(|| {
154                OperationOutcomeError::error(
155                    IssueType::NotFound(None),
156                    format!("Project '{}' not found.", model.id.as_ref()),
157                )
158            })
159    }
160}
161
162impl<Key: AsRef<str> + Send + Sync>
163    TenantAuthAdmin<CreateProject, Project, ProjectSearchClaims, Project, Key> for PGConnection
164{
165    async fn create(
166        &self,
167        tenant: &TenantId,
168        new_project: CreateProject,
169    ) -> Result<Project, OperationOutcomeError> {
170        match self {
171            PGConnection::Pool(pool, _) => {
172                let res = create_project(pool, tenant, new_project).await?;
173                Ok(res)
174            }
175            PGConnection::Transaction(tx, _) => {
176                let mut tx = tx.lock().await;
177                let res = create_project(&mut *tx, tenant, new_project).await?;
178                Ok(res)
179            }
180        }
181    }
182
183    async fn read(
184        &self,
185        tenant: &TenantId,
186        id: &Key,
187    ) -> Result<Option<Project>, haste_fhir_operation_error::OperationOutcomeError> {
188        match self {
189            PGConnection::Pool(pool, _) => {
190                let res = read_project(pool, tenant, id.as_ref()).await?;
191                Ok(res)
192            }
193            PGConnection::Transaction(tx, _) => {
194                let mut tx = tx.lock().await;
195                let res = read_project(&mut *tx, tenant, id.as_ref()).await?;
196                Ok(res)
197            }
198        }
199    }
200
201    async fn update(
202        &self,
203        tenant: &TenantId,
204        model: Project,
205    ) -> Result<Project, haste_fhir_operation_error::OperationOutcomeError> {
206        match self {
207            PGConnection::Pool(pool, _) => {
208                let res = update_project(pool, tenant, model).await?;
209                Ok(res)
210            }
211            PGConnection::Transaction(tx, _) => {
212                let mut tx = tx.lock().await;
213                let res = update_project(&mut *tx, tenant, model).await?;
214                Ok(res)
215            }
216        }
217    }
218
219    async fn delete(
220        &self,
221        tenant: &TenantId,
222        id: &Key,
223    ) -> Result<(), haste_fhir_operation_error::OperationOutcomeError> {
224        match self {
225            PGConnection::Pool(pool, _) => {
226                let res = delete_project(pool, tenant, id.as_ref()).await?;
227                Ok(res)
228            }
229            PGConnection::Transaction(tx, _) => {
230                let mut tx = tx.lock().await;
231                let res = delete_project(&mut *tx, tenant, id.as_ref()).await?;
232                Ok(res)
233            }
234        }
235    }
236
237    async fn search(
238        &self,
239        tenant: &TenantId,
240        claims: &ProjectSearchClaims,
241    ) -> Result<Vec<Project>, OperationOutcomeError> {
242        match self {
243            PGConnection::Pool(pool, _) => {
244                let res = search_project(pool, tenant, claims).await?;
245                Ok(res)
246            }
247            PGConnection::Transaction(tx, _) => {
248                let mut tx = tx.lock().await;
249                let res = search_project(&mut *tx, tenant, claims).await?;
250                Ok(res)
251            }
252        }
253    }
254}