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_jwt::{ProjectId, TenantId};
11use haste_fhir_model::r4::generated::terminology::IssueType;
12use haste_fhir_operation_error::OperationOutcomeError;
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!("Project '{}' not found or is system created and cannot be deleted.", id),
89            ));
90        }
91
92        Ok(())
93    }
94}
95
96fn search_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
97    connection: Connection,
98    tenant: &'a TenantId,
99    clauses: &'a ProjectSearchClaims,
100) -> impl Future<Output = Result<Vec<Project>, OperationOutcomeError>> + Send + 'a {
101    async move {
102        let mut conn = connection.acquire().await.map_err(StoreError::SQLXError)?;
103        let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
104            r#"SELECT tenant, id, fhir_version FROM projects WHERE "#,
105        );
106
107        let mut and_clauses = query_builder.separated(" AND ");
108
109        and_clauses
110            .push(" tenant = ")
111            .push_bind_unseparated(tenant.as_ref());
112
113        if let Some(id) = clauses.id.as_ref() {
114            and_clauses
115                .push(" id = ")
116                .push_bind_unseparated(id.as_ref());
117        }
118
119        if let Some(fhir_version) = clauses.fhir_version.as_ref() {
120            and_clauses
121                .push(" fhir_version = ")
122                .push_bind_unseparated(fhir_version);
123        }
124
125        if let Some(system_created) = clauses.system_created.as_ref() {
126            and_clauses
127                .push(" system_created = ")
128                .push_bind_unseparated(system_created);
129        }
130
131        let query = query_builder.build_query_as();
132
133        let projects: Vec<Project> = query
134            .fetch_all(&mut *conn)
135            .await
136            .map_err(StoreError::from)?;
137
138        Ok(projects)
139    }
140}
141
142/// Not allowing updates on internal row just reading to confirm it's existance.
143fn update_project<'a, 'c, Connection: Acquire<'c, Database = Postgres> + Send + 'a>(
144    connection: Connection,
145    tenant: &'a TenantId,
146    model: Project,
147) -> impl Future<Output = Result<Project, OperationOutcomeError>> + Send + 'a {
148    async move {
149        read_project(connection, tenant, model.id.as_ref()).await?
150            .ok_or_else(|| {
151                OperationOutcomeError::error(
152                    IssueType::NotFound(None),
153                    format!("Project '{}' not found.", model.id.as_ref()),
154                )
155            } )
156    }
157}
158
159impl<Key: AsRef<str> + Send + Sync> TenantAuthAdmin<CreateProject, Project, ProjectSearchClaims, Project, Key> for PGConnection {
160    async fn create(
161        &self,
162        tenant: &TenantId,
163        new_project: CreateProject,
164    ) -> Result<Project, OperationOutcomeError> {
165        match self {
166            PGConnection::Pool(pool, _) => {
167                let res = create_project(pool, tenant, new_project).await?;
168                Ok(res)
169            }
170            PGConnection::Transaction(tx, _) => {
171                let mut tx = tx.lock().await;
172                let res = create_project(&mut *tx, tenant, new_project).await?;
173                Ok(res)
174            }
175        }
176    }
177
178    async fn read(
179        &self,
180        tenant: &TenantId,
181        id: &Key,
182    ) -> Result<Option<Project>, haste_fhir_operation_error::OperationOutcomeError> {
183        match self {
184            PGConnection::Pool(pool, _) => {
185                let res = read_project(pool, tenant, id.as_ref()).await?;
186                Ok(res)
187            }
188            PGConnection::Transaction(tx, _) => {
189                let mut tx = tx.lock().await;
190                let res = read_project(&mut *tx, tenant, id.as_ref()).await?;
191                Ok(res)
192            }
193        }
194    }
195
196    async fn update(
197        &self,
198        tenant: &TenantId,
199        model: Project,
200    ) -> Result<Project, haste_fhir_operation_error::OperationOutcomeError> {
201        match self {
202            PGConnection::Pool(pool, _) => {
203                let res = update_project(pool, tenant, model).await?;
204                Ok(res)
205            }
206            PGConnection::Transaction(tx, _) => {
207                let mut tx = tx.lock().await;
208                let res = update_project(&mut *tx, tenant, model).await?;
209                Ok(res)
210            }
211        }
212    }
213
214    async fn delete(
215        &self,
216        tenant: &TenantId,
217        id: &Key,
218    ) -> Result<(), haste_fhir_operation_error::OperationOutcomeError> {
219        match self {
220            PGConnection::Pool(pool, _) => {
221                let res = delete_project(pool, tenant, id.as_ref()).await?;
222                Ok(res)
223            }
224            PGConnection::Transaction(tx, _) => {
225                let mut tx = tx.lock().await;
226                let res = delete_project(&mut *tx, tenant, id.as_ref()).await?;
227                Ok(res)
228            }
229        }
230    }
231
232    async fn search(
233        &self,
234        tenant: &TenantId,
235        claims: &ProjectSearchClaims,
236    ) -> Result<Vec<Project>, OperationOutcomeError> {
237        match self {
238            PGConnection::Pool(pool, _) => {
239                let res = search_project(pool, tenant, claims).await?;
240                Ok(res)
241            }
242            PGConnection::Transaction(tx, _) => {
243                let mut tx = tx.lock().await;
244                let res = search_project(&mut *tx, tenant, claims).await?;
245                Ok(res)
246            }
247        }
248    }
249}