1pub fn log_include_hydration_failure(table: &str, error: &str) {
24 tracing::error!(
25 relation_table = table,
26 error = error,
27 "include hydration failed; relation expanded as null"
28 );
29}
30
31#[macro_export]
68macro_rules! impl_crud_repository {
69 ($repo:ty, $entity:ty, soft_delete) => {
75 #[async_trait::async_trait]
76 impl backbone_core::CrudRepository<$entity> for $repo {
77 async fn fetch_related_json(
78 &self,
79 table: &str,
80 ids: &[String],
81 ) -> Vec<serde_json::Value> {
82 match backbone_orm::fetch_by_ids_as_json(
83 (&**self).pool(),
84 (&**self).table_name(),
85 table,
86 ids,
87 )
88 .await
89 {
90 Ok(rows) => rows,
91 Err(err) => {
92 backbone_core::log_include_hydration_failure(table, &err.to_string());
93 Vec::new()
94 }
95 }
96 }
97
98 async fn create(
106 &self,
107 entity: $entity,
108 ) -> Result<$entity, backbone_core::RepositoryError> {
109 (&**self).create(&entity)
110 .await
111 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
112 }
113
114 async fn find_by_id(
115 &self,
116 id: &str,
117 ) -> Result<Option<$entity>, backbone_core::RepositoryError> {
118 (&**self).find_by_id(id)
119 .await
120 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
121 }
122
123 async fn find_by_id_including_deleted(
124 &self,
125 id: &str,
126 ) -> Result<Option<$entity>, backbone_core::RepositoryError> {
127 (&**self).find_deleted_by_id(id)
128 .await
129 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
130 }
131
132 async fn update(
133 &self,
134 entity: $entity,
135 ) -> Result<$entity, backbone_core::RepositoryError> {
136 let id = backbone_core::PersistentEntity::entity_id(&entity);
137 (&**self).update(&id, &entity)
138 .await
139 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
140 .and_then(|opt| opt.ok_or(backbone_core::RepositoryError::NotFound))
141 }
142
143 async fn soft_delete(
144 &self,
145 id: &str,
146 ) -> Result<bool, backbone_core::RepositoryError> {
147 (&**self).soft_delete(id)
148 .await
149 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
150 }
151
152 async fn restore(
153 &self,
154 id: &str,
155 ) -> Result<Option<$entity>, backbone_core::RepositoryError> {
156 (&**self).restore(id)
157 .await
158 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
159 }
160
161 async fn hard_delete(
162 &self,
163 id: &str,
164 ) -> Result<bool, backbone_core::RepositoryError> {
165 (&**self).permanent_delete(id)
166 .await
167 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
168 }
169
170 async fn list(
171 &self,
172 page: u32,
173 limit: u32,
174 ) -> Result<(Vec<$entity>, u64), backbone_core::RepositoryError> {
175 let pagination =
176 backbone_orm::repository::PaginationParams { page, per_page: limit };
177 (&**self).list_paginated(pagination)
178 .await
179 .map(|r| (r.data, r.pagination.total))
180 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
181 }
182
183 async fn list_filtered(
184 &self,
185 page: u32,
186 limit: u32,
187 filters: std::collections::HashMap<String, String>,
188 ) -> Result<(Vec<$entity>, u64), backbone_core::RepositoryError> {
189 let pagination =
190 backbone_orm::repository::PaginationParams { page, per_page: limit };
191 (&**self).list_paginated_filtered(pagination, Some(&filters))
192 .await
193 .map(|r| (r.data, r.pagination.total))
194 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
195 }
196
197 async fn list_filtered_with_info(
198 &self,
199 page: u32,
200 limit: u32,
201 filters: std::collections::HashMap<String, String>,
202 ) -> Result<
203 (Vec<$entity>, backbone_orm::repository::PaginationInfo),
204 backbone_core::RepositoryError,
205 > {
206 let pagination =
207 backbone_orm::repository::PaginationParams { page, per_page: limit };
208 (&**self)
209 .list_paginated_filtered(pagination, Some(&filters))
210 .await
211 .map(|r| (r.data, r.pagination))
212 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
213 }
214
215 fn table_name(&self) -> Option<&str> {
216 Some((&**self).table_name())
217 }
218
219 async fn aggregate_filtered(
220 &self,
221 spec: &backbone_orm::repository::AggregateSpec,
222 filters: std::collections::HashMap<String, String>,
223 ) -> Result<backbone_orm::repository::AggregateResult, backbone_core::RepositoryError> {
224 (&**self).aggregate_filtered(spec, Some(&filters))
225 .await
226 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
227 }
228
229 async fn list_deleted(
230 &self,
231 page: u32,
232 limit: u32,
233 ) -> Result<(Vec<$entity>, u64), backbone_core::RepositoryError> {
234 let pagination =
235 backbone_orm::repository::PaginationParams { page, per_page: limit };
236 (&**self).list_deleted(pagination)
237 .await
238 .map(|r| (r.data, r.pagination.total))
239 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
240 }
241
242 async fn count(&self) -> Result<u64, backbone_core::RepositoryError> {
243 (&**self).count_active()
244 .await
245 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
246 }
247
248 async fn count_deleted(&self) -> Result<u64, backbone_core::RepositoryError> {
249 (&**self).count_deleted()
250 .await
251 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
252 }
253
254 async fn bulk_create(
255 &self,
256 entities: Vec<$entity>,
257 ) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
258 let mut results = Vec::with_capacity(entities.len());
259 for entity in entities {
260 let created = (&**self)
261 .create(&entity)
262 .await
263 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))?;
264 results.push(created);
265 }
266 Ok(results)
267 }
268
269 async fn empty_trash(&self) -> Result<u64, backbone_core::RepositoryError> {
270 (&**self).empty_trash()
271 .await
272 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
273 }
274
275 async fn bulk_soft_delete(
278 &self,
279 ids: &[String],
280 ) -> Result<u64, backbone_core::RepositoryError> {
281 (&**self).bulk_soft_delete(ids)
282 .await
283 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
284 }
285
286 async fn bulk_restore(
287 &self,
288 ids: &[String],
289 ) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
290 (&**self).bulk_restore(ids)
291 .await
292 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
293 }
294
295 async fn bulk_hard_delete(
296 &self,
297 ids: &[String],
298 ) -> Result<u64, backbone_core::RepositoryError> {
299 (&**self).bulk_permanent_delete(ids)
300 .await
301 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
302 }
303
304 async fn restore_all(&self) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
305 (&**self).restore_all()
306 .await
307 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
308 }
309
310 async fn bulk_update(
311 &self,
312 entities: Vec<$entity>,
313 ) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
314 (&**self).bulk_update(&entities)
315 .await
316 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
317 }
318 }
319 };
320
321 ($repo:ty, $entity:ty, no_soft_delete) => {
326 #[async_trait::async_trait]
327 impl backbone_core::CrudRepository<$entity> for $repo {
328 async fn fetch_related_json(
329 &self,
330 table: &str,
331 ids: &[String],
332 ) -> Vec<serde_json::Value> {
333 match backbone_orm::fetch_by_ids_as_json(
334 (&**self).pool(),
335 (&**self).table_name(),
336 table,
337 ids,
338 )
339 .await
340 {
341 Ok(rows) => rows,
342 Err(err) => {
343 backbone_core::log_include_hydration_failure(table, &err.to_string());
344 Vec::new()
345 }
346 }
347 }
348
349 async fn create(
350 &self,
351 entity: $entity,
352 ) -> Result<$entity, backbone_core::RepositoryError> {
353 (&**self).create(&entity)
354 .await
355 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
356 }
357
358 async fn find_by_id(
359 &self,
360 id: &str,
361 ) -> Result<Option<$entity>, backbone_core::RepositoryError> {
362 (&**self).find_by_id(id)
363 .await
364 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
365 }
366
367 async fn find_by_id_including_deleted(
368 &self,
369 id: &str,
370 ) -> Result<Option<$entity>, backbone_core::RepositoryError> {
371 (&**self).find_by_id(id)
373 .await
374 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
375 }
376
377 async fn update(
378 &self,
379 entity: $entity,
380 ) -> Result<$entity, backbone_core::RepositoryError> {
381 let id = backbone_core::PersistentEntity::entity_id(&entity);
382 (&**self).update(&id, &entity)
383 .await
384 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
385 .and_then(|opt| opt.ok_or(backbone_core::RepositoryError::NotFound))
386 }
387
388 async fn soft_delete(
389 &self,
390 id: &str,
391 ) -> Result<bool, backbone_core::RepositoryError> {
392 (&**self).delete(id)
394 .await
395 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
396 }
397
398 async fn restore(
399 &self,
400 id: &str,
401 ) -> Result<Option<$entity>, backbone_core::RepositoryError> {
402 (&**self).find_by_id(id)
404 .await
405 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
406 }
407
408 async fn hard_delete(
409 &self,
410 id: &str,
411 ) -> Result<bool, backbone_core::RepositoryError> {
412 (&**self).delete(id)
413 .await
414 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
415 }
416
417 async fn list(
418 &self,
419 page: u32,
420 limit: u32,
421 ) -> Result<(Vec<$entity>, u64), backbone_core::RepositoryError> {
422 let pagination =
423 backbone_orm::repository::PaginationParams { page, per_page: limit };
424 (&**self).list_paginated(pagination)
425 .await
426 .map(|r| (r.data, r.pagination.total))
427 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
428 }
429
430 async fn list_filtered(
431 &self,
432 page: u32,
433 limit: u32,
434 filters: std::collections::HashMap<String, String>,
435 ) -> Result<(Vec<$entity>, u64), backbone_core::RepositoryError> {
436 let pagination =
437 backbone_orm::repository::PaginationParams { page, per_page: limit };
438 (&**self).list_paginated_filtered(pagination, Some(&filters))
439 .await
440 .map(|r| (r.data, r.pagination.total))
441 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
442 }
443
444 async fn list_filtered_with_info(
445 &self,
446 page: u32,
447 limit: u32,
448 filters: std::collections::HashMap<String, String>,
449 ) -> Result<
450 (Vec<$entity>, backbone_orm::repository::PaginationInfo),
451 backbone_core::RepositoryError,
452 > {
453 let pagination =
454 backbone_orm::repository::PaginationParams { page, per_page: limit };
455 (&**self)
456 .list_paginated_filtered(pagination, Some(&filters))
457 .await
458 .map(|r| (r.data, r.pagination))
459 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
460 }
461
462 fn table_name(&self) -> Option<&str> {
463 Some((&**self).table_name())
464 }
465
466 async fn aggregate_filtered(
467 &self,
468 spec: &backbone_orm::repository::AggregateSpec,
469 filters: std::collections::HashMap<String, String>,
470 ) -> Result<backbone_orm::repository::AggregateResult, backbone_core::RepositoryError> {
471 (&**self).aggregate_filtered(spec, Some(&filters))
472 .await
473 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
474 }
475
476 async fn list_deleted(
477 &self,
478 _page: u32,
479 _limit: u32,
480 ) -> Result<(Vec<$entity>, u64), backbone_core::RepositoryError> {
481 Ok((vec![], 0))
483 }
484
485 async fn count(&self) -> Result<u64, backbone_core::RepositoryError> {
486 (&**self).count()
487 .await
488 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
489 }
490
491 async fn count_deleted(&self) -> Result<u64, backbone_core::RepositoryError> {
492 Ok(0)
493 }
494
495 async fn bulk_create(
496 &self,
497 entities: Vec<$entity>,
498 ) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
499 let mut results = Vec::with_capacity(entities.len());
500 for entity in entities {
501 let created = (&**self)
502 .create(&entity)
503 .await
504 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))?;
505 results.push(created);
506 }
507 Ok(results)
508 }
509
510 async fn empty_trash(&self) -> Result<u64, backbone_core::RepositoryError> {
511 Ok(0)
512 }
513
514 async fn bulk_soft_delete(
517 &self,
518 ids: &[String],
519 ) -> Result<u64, backbone_core::RepositoryError> {
520 (&**self).bulk_delete(ids)
522 .await
523 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
524 }
525
526 async fn bulk_restore(
527 &self,
528 _ids: &[String],
529 ) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
530 Ok(Vec::new())
532 }
533
534 async fn bulk_hard_delete(
535 &self,
536 ids: &[String],
537 ) -> Result<u64, backbone_core::RepositoryError> {
538 (&**self).bulk_delete(ids)
539 .await
540 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
541 }
542
543 async fn restore_all(&self) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
544 Ok(Vec::new())
546 }
547
548 async fn bulk_update(
549 &self,
550 entities: Vec<$entity>,
551 ) -> Result<Vec<$entity>, backbone_core::RepositoryError> {
552 (&**self).bulk_update(&entities)
553 .await
554 .map_err(|e| backbone_core::RepositoryError::DatabaseError(e.to_string()))
555 }
556 }
557 };
558}