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