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
144fn 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}