1use crate::{
13 pg::StoreError,
14 types::{FHIRMethod, SupportedFHIRVersions},
15};
16use haste_fhir_model::r4::{generated::resources::Resource, sqlx::FHIRJsonRef};
17use haste_fhir_operation_error::OperationOutcomeError;
18use haste_jwt::{AuthorId, AuthorKind, ProjectId, TenantId, claims::UserTokenClaims};
19use sqlx::{PgExecutor, Postgres, QueryBuilder};
20use std::sync::Arc;
21use tokio::sync::Mutex;
22
23trait ResourceRowFields {
28 fn tenant(&self) -> &TenantId;
29 fn project(&self) -> &ProjectId;
30 fn author_id(&self) -> &AuthorId;
31 fn author_type(&self) -> &AuthorKind;
32 fn fhir_version(&self) -> &SupportedFHIRVersions;
33 fn resource(&self) -> &Resource;
34 fn deleted(&self) -> bool;
35 fn request_method(&self) -> &str;
36 fn fhir_method(&self) -> &FHIRMethod;
37}
38
39#[derive(Debug, Clone)]
41struct PendingResourceRow {
42 tenant: TenantId,
43 project: ProjectId,
44 author_id: AuthorId,
45 author_type: AuthorKind,
46 fhir_version: SupportedFHIRVersions,
47 resource: Resource,
48 deleted: bool,
49 request_method: &'static str,
50 fhir_method: FHIRMethod,
51}
52
53impl ResourceRowFields for PendingResourceRow {
54 fn tenant(&self) -> &TenantId {
55 &self.tenant
56 }
57 fn project(&self) -> &ProjectId {
58 &self.project
59 }
60 fn author_id(&self) -> &AuthorId {
61 &self.author_id
62 }
63 fn author_type(&self) -> &AuthorKind {
64 &self.author_type
65 }
66 fn fhir_version(&self) -> &SupportedFHIRVersions {
67 &self.fhir_version
68 }
69 fn resource(&self) -> &Resource {
70 &self.resource
71 }
72 fn deleted(&self) -> bool {
73 self.deleted
74 }
75 fn request_method(&self) -> &str {
76 self.request_method
77 }
78 fn fhir_method(&self) -> &FHIRMethod {
79 &self.fhir_method
80 }
81}
82
83struct BorrowedResourceRow<'a> {
87 tenant: &'a TenantId,
88 project: &'a ProjectId,
89 author_id: &'a AuthorId,
90 author_type: &'a AuthorKind,
91 fhir_version: &'a SupportedFHIRVersions,
92 resource: &'a Resource,
93 deleted: bool,
94 request_method: &'static str,
95 fhir_method: FHIRMethod,
96}
97
98impl ResourceRowFields for BorrowedResourceRow<'_> {
99 fn tenant(&self) -> &TenantId {
100 self.tenant
101 }
102 fn project(&self) -> &ProjectId {
103 self.project
104 }
105 fn author_id(&self) -> &AuthorId {
106 self.author_id
107 }
108 fn author_type(&self) -> &AuthorKind {
109 self.author_type
110 }
111 fn fhir_version(&self) -> &SupportedFHIRVersions {
112 self.fhir_version
113 }
114 fn resource(&self) -> &Resource {
115 self.resource
116 }
117 fn deleted(&self) -> bool {
118 self.deleted
119 }
120 fn request_method(&self) -> &str {
121 self.request_method
122 }
123 fn fhir_method(&self) -> &FHIRMethod {
124 &self.fhir_method
125 }
126}
127
128#[allow(clippy::too_many_arguments)]
132pub async fn execute<'e, E>(
133 executor: E,
134 tenant: &TenantId,
135 project: &ProjectId,
136 author: &UserTokenClaims,
137 fhir_version: &SupportedFHIRVersions,
138 resource: &Resource,
139 deleted: bool,
140 request_method: &'static str,
141 fhir_method: FHIRMethod,
142) -> Result<(), OperationOutcomeError>
143where
144 E: PgExecutor<'e>,
145{
146 let row = BorrowedResourceRow {
147 tenant,
148 project,
149 author_id: &author.sub,
150 author_type: &author.resource_type,
151 fhir_version,
152 resource,
153 deleted,
154 request_method,
155 fhir_method,
156 };
157
158 insert_resource_updates(executor, std::slice::from_ref(&row)).await
159}
160
161#[derive(Debug, Clone, Default)]
166pub struct PendingRows(Arc<Mutex<Vec<PendingResourceRow>>>);
167
168impl PendingRows {
169 #[must_use]
170 pub fn new() -> Self {
171 Self::default()
172 }
173
174 #[allow(clippy::too_many_arguments)]
177 pub async fn push(
178 &self,
179 tenant: &TenantId,
180 project: &ProjectId,
181 author: &UserTokenClaims,
182 fhir_version: &SupportedFHIRVersions,
183 resource: Resource,
184 deleted: bool,
185 request_method: &'static str,
186 fhir_method: FHIRMethod,
187 ) {
188 self.0.lock().await.push(PendingResourceRow {
189 tenant: tenant.clone(),
190 project: project.clone(),
191 author_id: author.sub.clone(),
192 author_type: author.resource_type.clone(),
193 fhir_version: fhir_version.clone(),
194 resource,
195 deleted,
196 request_method,
197 fhir_method,
198 });
199 }
200
201 pub async fn flush(
211 &self,
212 tx: &Arc<Mutex<sqlx::Transaction<'static, Postgres>>>,
213 ) -> Result<(), OperationOutcomeError> {
214 let rows = {
215 let mut guard = self.0.lock().await;
216 std::mem::take(&mut *guard)
217 };
218
219 if rows.is_empty() {
220 return Ok(());
221 }
222
223 let mut conn = tx.lock().await;
224 insert_resource_updates(&mut **conn, &rows).await
225 }
226}
227
228async fn insert_resource_updates<'e, E, R>(
231 executor: E,
232 rows: &[R],
233) -> Result<(), OperationOutcomeError>
234where
235 E: PgExecutor<'e>,
236 R: ResourceRowFields,
237{
238 if rows.is_empty() {
239 return Ok(());
240 }
241
242 let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
243 "INSERT INTO resources (tenant, project, author_id, fhir_version, resource, deleted, request_method, author_type, fhir_method) ",
244 );
245
246 query_builder.push_values(rows, |mut b, row| {
247 b.push_bind(row.tenant().as_ref())
248 .push_bind(row.project().as_ref())
249 .push_bind(row.author_id().as_ref())
250 .push_bind(row.fhir_version())
251 .push_bind(FHIRJsonRef(row.resource()))
252 .push_bind(row.deleted())
253 .push_bind(row.request_method())
254 .push_bind(row.author_type().as_ref())
255 .push_bind(row.fhir_method());
256 });
257
258 query_builder
259 .build()
260 .execute(executor)
261 .await
262 .map_err(StoreError::from)?;
263
264 Ok(())
265}