1use std::{
2 borrow::Cow,
3 future::{Future, IntoFuture},
4 marker::PhantomData,
5 pin::Pin,
6 str::FromStr,
7 sync::Arc,
8};
9
10use async_graphql::{Description, InputValueError, InputValueResult, Scalar, ScalarType, Value};
11use chrono::{DateTime, Utc};
12use futures::{StreamExt as _, TryStreamExt as _};
13use serde::{de::DeserializeOwned, Deserialize, Serialize};
14
15use qm_mongodb::{
16 bson::{
17 doc, oid::ObjectId, serde_helpers::chrono_datetime_as_bson_datetime, to_bson, Bson,
18 Document, Uuid,
19 },
20 options::FindOptions,
21 Collection, Database,
22};
23
24use crate::{
25 error::EntityError,
26 ids::{
27 CustomerId, CustomerOrOrganization, CustomerResourceId, InstitutionId,
28 InstitutionResourceId, OrganizationId, OrganizationOrInstitution, OrganizationResourceId,
29 OwnerId,
30 },
31 model::ListFilter,
32};
33
34const EMPTY_ID: &str = "000000000000000000000000";
35const DEFAULT_PAGE_LIMIT: i64 = 100;
36
37#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, Description)]
39pub struct Id(ObjectId);
40
41impl FromStr for Id {
42 type Err = ();
43
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 parse_object_id(s)?.ok_or(())
46 }
47}
48
49fn parse_object_id(id: &str) -> Result<Option<Id>, ()> {
50 if id == EMPTY_ID {
51 Ok(None)
52 } else {
53 Ok(Some(ObjectId::from_str(id).map(Id).map_err(|_| ())?))
54 }
55}
56
57#[Scalar]
58impl ScalarType for Id {
59 fn parse(value: Value) -> InputValueResult<Self> {
60 if let Value::String(value) = &value {
61 Ok(Self::from_str(value).map_err(|_| InputValueError::custom("parse error"))?)
62 } else {
63 Err(InputValueError::expected_type(value))
64 }
65 }
66
67 fn to_value(&self) -> Value {
68 Value::String(self.0.to_hex())
69 }
70}
71
72type ID = Id;
73
74#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, Description)]
76#[graphql(name = "EntityId")]
77pub struct GraphQLId {
78 #[graphql(flatten)]
79 id: ID,
80}
81
82impl FromStr for GraphQLId {
83 type Err = ();
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 parse_object_id(s)?.map(|id| Self { id }).ok_or(())
87 }
88}
89
90impl AsRef<ObjectId> for GraphQLId {
91 fn as_ref(&self) -> &ObjectId {
92 &self.id.0
93 }
94}
95
96#[Scalar]
97impl ScalarType for GraphQLId {
98 fn parse(value: Value) -> InputValueResult<Self> {
99 if let Value::String(value) = &value {
100 Ok(Self::from_str(value).map_err(|_| InputValueError::custom("parse error"))?)
101 } else {
102 Err(InputValueError::expected_type(value))
103 }
104 }
105
106 fn to_value(&self) -> Value {
107 Value::String(self.id.0.to_hex())
108 }
109}
110
111impl From<Id> for GraphQLId {
112 fn from(id: Id) -> Self {
113 Self { id }
114 }
115}
116
117pub trait ToMongoFilterMany {
118 fn to_mongo_filter_many(&self) -> Option<Document>;
119}
120
121impl ToMongoFilterMany for () {
122 fn to_mongo_filter_many(&self) -> Option<Document> {
123 None
124 }
125}
126
127impl ToMongoFilterMany for Option<Document> {
128 fn to_mongo_filter_many(&self) -> Option<Document> {
129 self.clone()
130 }
131}
132
133impl<T> ToMongoFilterMany for Option<T>
134where
135 T: ToMongoFilterMany,
136{
137 fn to_mongo_filter_many(&self) -> Option<Document> {
138 self.as_ref().and_then(|v| v.to_mongo_filter_many())
139 }
140}
141
142impl ToMongoFilterMany for CustomerId {
143 fn to_mongo_filter_many(&self) -> Option<Document> {
144 let cid = self.unzip();
145 Some(doc! { "owner.cid": cid })
146 }
147}
148
149impl ToMongoFilterMany for OrganizationId {
150 fn to_mongo_filter_many(&self) -> Option<Document> {
151 let (cid, oid) = self.unzip();
152 Some(doc! { "owner.cid": cid, "owner.oid": oid })
153 }
154}
155
156impl ToMongoFilterMany for InstitutionId {
157 fn to_mongo_filter_many(&self) -> Option<Document> {
158 let (cid, oid, iid) = self.unzip();
159 Some(doc! { "owner.cid": cid, "owner.oid": oid, "owner.iid": iid })
160 }
161}
162
163impl ToMongoFilterMany for CustomerOrOrganization {
164 fn to_mongo_filter_many(&self) -> Option<Document> {
165 match self {
166 Self::Customer(v) => v.to_mongo_filter_many(),
167 Self::Organization(v) => v.to_mongo_filter_many(),
168 }
169 }
170}
171
172impl ToMongoFilterMany for OrganizationOrInstitution {
173 fn to_mongo_filter_many(&self) -> Option<Document> {
174 match self {
175 Self::Institution(v) => v.to_mongo_filter_many(),
176 Self::Organization(v) => v.to_mongo_filter_many(),
177 }
178 }
179}
180
181pub trait ToMongoFilterOne {
182 fn to_mongo_filter_one(&self) -> Document;
183}
184
185impl ToMongoFilterOne for Document {
186 fn to_mongo_filter_one(&self) -> Document {
187 self.clone()
188 }
189}
190
191impl ToMongoFilterOne for CustomerResourceId {
192 fn to_mongo_filter_one(&self) -> Document {
193 let (.., id) = self.unzip();
194 doc! { "_id": id }
195 }
196}
197
198impl ToMongoFilterOne for OrganizationResourceId {
199 fn to_mongo_filter_one(&self) -> Document {
200 doc! { "_id": self.id() }
201 }
202}
203
204impl ToMongoFilterOne for InstitutionResourceId {
205 fn to_mongo_filter_one(&self) -> Document {
206 let (.., id) = self.unzip();
207 doc! { "_id": id }
208 }
209}
210
211impl ToMongoFilterOne for CustomerId {
212 fn to_mongo_filter_one(&self) -> Document {
213 doc! { "_id": self.unzip() }
214 }
215}
216
217impl ToMongoFilterOne for OrganizationId {
218 fn to_mongo_filter_one(&self) -> Document {
219 doc! { "_id": self.id() }
220 }
221}
222
223impl ToMongoFilterOne for InstitutionId {
224 fn to_mongo_filter_one(&self) -> Document {
225 doc! { "_id": self.id() }
226 }
227}
228
229impl ToMongoFilterOne for CustomerOrOrganization {
230 fn to_mongo_filter_one(&self) -> Document {
231 match self {
232 Self::Customer(v) => v.to_mongo_filter_one(),
233 Self::Organization(v) => v.to_mongo_filter_one(),
234 }
235 }
236}
237
238impl ToMongoFilterOne for OrganizationOrInstitution {
239 fn to_mongo_filter_one(&self) -> Document {
240 match self {
241 Self::Institution(v) => v.to_mongo_filter_one(),
242 Self::Organization(v) => v.to_mongo_filter_one(),
243 }
244 }
245}
246
247pub trait ToMongoFilterExact {
248 fn to_mongo_filter_exact(&self) -> Result<Document, EntityError>;
249}
250
251pub struct ResourcesFilter<'a, I>(pub &'a [I])
252where
253 I: ToMongoFilterOne;
254impl<I> ToMongoFilterExact for ResourcesFilter<'_, I>
255where
256 I: ToMongoFilterOne,
257{
258 fn to_mongo_filter_exact(&self) -> Result<Document, EntityError> {
259 if self.0.is_empty() {
260 return Err(EntityError::NotEmpty);
261 }
262 if self.0.len() == 1 {
263 return Ok(self.0.first().unwrap().to_mongo_filter_one());
264 }
265 let items: Vec<Document> = self.0.iter().map(|v| v.to_mongo_filter_one()).collect();
266 Ok(doc! {
267 "$or": items,
268 })
269 }
270}
271
272pub trait AsMongoId {
273 fn as_mongo_id(&self) -> ObjectId;
274}
275
276pub trait FromMongoId: Sized {
277 fn from_mongo_id(old_id: Self, bson: Bson) -> Option<Self>;
278}
279
280pub trait IsMongoInsert {
281 fn is_mongo_insert(&self) -> bool;
282}
283
284#[derive(Debug, Deserialize, Serialize)]
285pub struct Entity<T> {
286 id: ID,
287 #[serde(flatten)]
288 fields: T,
289 #[serde(flatten)]
290 defaults: Defaults,
291}
292
293#[derive(Debug, Default, Deserialize, Serialize)]
294pub struct Page<I> {
295 pub items: Vec<I>,
296 pub skip: u64,
297 pub limit: Option<i64>,
298 pub total: usize,
299}
300
301impl<I> Page<I> {
302 pub fn empty() -> Self {
304 Self {
305 items: vec![],
306 total: 0,
307 skip: 0,
308 limit: Some(DEFAULT_PAGE_LIMIT),
309 }
310 }
311
312 pub fn index(&self) -> u64 {
314 if let Some(limit) = self.limit.filter(|l| *l > 0).map(|l| l as u64) {
315 self.skip / limit
316 } else {
317 0
318 }
319 }
320
321 pub fn count(&self) -> usize {
323 if let Some(limit) = self.limit.filter(|l| *l > 0).map(|l| l as usize) {
324 self.total.div_ceil(limit)
325 } else {
326 0
327 }
328 }
329}
330
331#[derive(Default)]
332pub struct PageInfo {
333 skip: Option<u64>,
334 limit: Option<i64>,
335}
336
337impl TryFrom<ListFilter> for PageInfo {
338 type Error = EntityError;
339
340 fn try_from(value: ListFilter) -> Result<Self, Self::Error> {
341 let limit = value.limit.map(|l| l as i64).unwrap_or(DEFAULT_PAGE_LIMIT);
342 Ok(Self {
343 skip: value.page.map(|page| limit as u64 * page as u64),
344 limit: Some(limit),
345 })
346 }
347}
348
349impl TryFrom<Option<ListFilter>> for PageInfo {
350 type Error = EntityError;
351
352 fn try_from(value: Option<ListFilter>) -> Result<Self, Self::Error> {
353 value
354 .map(|v| v.try_into())
355 .unwrap_or_else(|| Ok(Default::default()))
356 }
357}
358
359pub trait UpdateEntity<T: Clone> {
360 fn update_entity(self, entity: &T) -> Result<Cow<'_, T>, EntityError>;
361}
362
363#[derive(Debug, Deserialize, Serialize, Clone)]
364pub struct EntityOwned<T, ID = Id> {
365 #[serde(rename = "_id")]
366 pub id: ID,
367 pub owner: Arc<OwnerId>,
368 #[serde(flatten)]
369 pub fields: T,
370 #[serde(flatten)]
371 pub defaults: Arc<Defaults>,
372}
373
374impl<T> EntityOwned<T>
375where
376 T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
377{
378 pub async fn create(
379 db: &Database,
380 owner: impl Into<OwnerId>,
381 fields: T,
382 user_id: Uuid,
383 ) -> Result<Self, EntityError> {
384 #[derive(Serialize)]
385 struct CreateOwnedEntity<'f, F> {
386 owner: Arc<OwnerId>,
387 #[serde(flatten)]
388 fields: &'f F,
389 #[serde(flatten)]
390 defaults: Arc<Defaults>,
391 }
392
393 let owner = Arc::new(owner.into());
394 let defaults = Arc::new(Defaults::now(user_id));
395
396 T::mongo_collection(db)
397 .insert_one(CreateOwnedEntity {
398 owner: owner.clone(),
399 fields: &fields,
400 defaults: defaults.clone(),
401 })
402 .await?
403 .inserted_id
404 .as_object_id()
405 .map(Id)
406 .ok_or(EntityError::NoId)
407 .map(|id| Self {
408 id,
409 owner,
410 fields,
411 defaults,
412 })
413 }
414}
415
416impl<T, ID> EntityOwned<T, ID>
417where
418 T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
419 ID: DeserializeOwned + Serialize + Send + Sync + Unpin,
420{
421 pub fn query(db: &Database) -> Query<'_, T, ID> {
423 Query::new(db)
424 }
425
426 pub async fn list(
427 db: &Database,
428 filter: impl ToMongoFilterMany,
429 ) -> Result<Vec<Self>, EntityError> {
430 T::mongo_collection(db)
431 .find(filter.to_mongo_filter_many().unwrap_or_default())
432 .await?
433 .try_collect()
434 .await
435 .map_err(From::from)
436 }
437
438 pub async fn page(
439 db: &Database,
440 filter: impl ToMongoFilterMany,
441 page_selector: impl TryInto<PageInfo, Error = EntityError>,
442 ) -> Result<Page<Self>, EntityError> {
443 Self::page_filter(
444 db,
445 filter.to_mongo_filter_many().unwrap_or_default(),
446 page_selector,
447 )
448 .await
449 }
450
451 pub async fn list_exact(
452 db: &Database,
453 filter: impl ToMongoFilterExact,
454 ) -> Result<Vec<Self>, EntityError> {
455 T::mongo_collection(db)
456 .find(filter.to_mongo_filter_exact()?)
457 .await?
458 .try_collect()
459 .await
460 .map_err(From::from)
461 }
462
463 pub async fn page_exact(
464 db: &Database,
465 filter: impl ToMongoFilterExact,
466 page_selector: impl TryInto<PageInfo, Error = EntityError>,
467 ) -> Result<Page<Self>, EntityError> {
468 Self::page_filter(db, filter.to_mongo_filter_exact()?, page_selector).await
469 }
470
471 pub async fn by_id(
472 db: &Database,
473 id: impl ToMongoFilterOne,
474 ) -> Result<Option<Self>, EntityError> {
475 T::mongo_collection(db)
476 .find_one(id.to_mongo_filter_one())
477 .await
478 .map_err(From::from)
479 }
480
481 pub async fn update(
482 db: &Database,
483 context: impl ToMongoFilterOne,
484 input: impl UpdateEntity<T>,
485 user_id: Uuid,
486 ) -> Result<Self, EntityError>
487 where
488 T: Clone,
489 {
490 let filter = context.to_mongo_filter_one();
491 let Some(mut entity): Option<Self> =
492 T::mongo_collection(db).find_one(filter.clone()).await?
493 else {
494 return Err(EntityError::NotFound);
495 };
496
497 if let Cow::Owned(updated) = input.update_entity(&entity.fields)? {
498 entity.fields = updated;
499 entity.defaults = Arc::new(entity.defaults.update_by(user_id));
500
501 if let Some(filter) = filter.into() {
502 T::mongo_collection::<Self>(db)
503 .replace_one(filter, &entity)
504 .await?;
505 }
506 }
507
508 Ok(entity)
509 }
510
511 pub async fn save<C>(
512 db: &Database,
513 context: C,
514 input: impl Into<T>,
515 user_id: Uuid,
516 ) -> Result<bool, EntityError>
517 where
518 T: Clone + std::fmt::Debug,
519 C: ToMongoFilterOne + Into<OwnerId>,
520 {
521 let filter = context.to_mongo_filter_one();
522 #[derive(Debug, Serialize)]
523 struct SaveEntity<F> {
524 owner: OwnerId,
525 #[serde(flatten)]
526 fields: F,
527 #[serde(flatten)]
528 defaults: Arc<Defaults>,
529 }
530 let defaults = Arc::new(Defaults::now(user_id));
531 let entity = SaveEntity {
532 owner: context.into(),
533 fields: input.into(),
534 defaults,
535 };
536 let result = T::mongo_collection::<SaveEntity<_>>(db)
537 .replace_one(filter, &entity)
538 .upsert(true)
539 .await?;
540 Ok(result.modified_count > 0 || result.upserted_id.is_some())
541 }
542
543 pub async fn save_with_id<C, I>(
544 db: &Database,
545 context: C,
546 input: I,
547 user_id: Uuid,
548 ) -> Result<Option<C>, EntityError>
549 where
550 T: Clone + std::fmt::Debug,
551 C: FromMongoId + IsMongoInsert + ToMongoFilterOne + Into<OwnerId> + Clone,
552 I: Into<T> + Send + Sync,
553 {
554 let filter = context.to_mongo_filter_one();
555 Ok(if context.is_mongo_insert() {
556 #[derive(Debug, Serialize)]
557 struct SaveEntity<F> {
558 owner: OwnerId,
559 #[serde(flatten)]
560 fields: F,
561 #[serde(flatten)]
562 defaults: Defaults,
563 }
564 let defaults = Defaults::now(user_id);
565 let entity = SaveEntity {
566 owner: context.clone().into(),
567 fields: input.into(),
568 defaults,
569 };
570 let result = T::mongo_collection::<SaveEntity<T>>(db)
571 .insert_one(&entity)
572 .await?;
573 C::from_mongo_id(context, result.inserted_id)
574 } else {
575 #[derive(Debug, Serialize)]
576 struct SaveEntity<F> {
577 owner: OwnerId,
578 #[serde(flatten)]
579 fields: F,
580 modified: UserModification,
581 }
582 let entity = SaveEntity {
583 owner: context.clone().into(),
584 fields: input.into(),
585 modified: UserModification::now(user_id),
586 };
587 let result = T::mongo_collection::<SaveEntity<T>>(db)
588 .update_one(filter, doc!{ "$set": to_bson(&entity).map_err(|err| EntityError::Bson(err.to_string()))? })
589 .await?;
590 if result.matched_count == 0 {
591 return Err(EntityError::NotFound);
592 }
593 if result.modified_count > 0 {
594 Some(context)
595 } else {
596 None
597 }
598 })
599 }
600
601 pub async fn remove<I>(db: &Database, ids: I) -> Result<i32, EntityError>
602 where
603 I: ToMongoFilterExact,
604 {
605 let result = T::mongo_collection::<Document>(db)
606 .delete_many(ids.to_mongo_filter_exact()?)
607 .await?;
608 Ok(result.deleted_count as i32)
609 }
610
611 pub async fn page_filter(
612 db: &Database,
613 filter: Document,
614 page_selector: impl TryInto<PageInfo, Error = EntityError>,
615 ) -> Result<Page<Self>, EntityError> {
616 let page_info: PageInfo = page_selector.try_into()?;
617 Self::page_filter_sort(db, filter, None, page_info).await
618 }
619
620 pub async fn page_filter_sort(
621 db: &Database,
622 filter: Document,
623 sort: Option<Document>,
624 page_info: PageInfo,
625 ) -> Result<Page<Self>, EntityError> {
626 let total = T::mongo_collection::<Self>(db)
627 .find(filter.clone())
628 .await?
629 .count()
630 .await;
631
632 let limit = page_info.limit;
633
634 if total == 0 {
635 return Ok(if limit.is_some() {
636 Page {
637 limit,
638 ..Page::empty()
639 }
640 } else {
641 Page::empty()
642 });
643 }
644
645 T::mongo_collection(db)
646 .find(filter)
647 .with_options(
648 FindOptions::builder()
649 .limit(limit)
650 .sort(sort)
651 .skip(page_info.skip)
652 .build(),
653 )
654 .await?
655 .try_collect()
656 .await
657 .map(|items| Page {
658 items,
659 total,
660 skip: page_info.skip.unwrap_or_default(),
661 limit,
662 })
663 .map_err(From::from)
664 }
665}
666
667pub struct Query<'q, T, ID> {
669 db: &'q Database,
670 filter: Option<Document>,
671 page: Option<PageInfo>,
672 sort: Option<Document>,
673 marker: PhantomData<(T, ID)>,
674}
675
676impl<'q, T, ID> Query<'q, T, ID> {
677 fn new(db: &'q Database) -> Self {
678 Self {
679 db,
680 filter: None,
681 page: None,
682 sort: None,
683 marker: Default::default(),
684 }
685 }
686
687 pub fn filter_exact(mut self, filter: impl ToMongoFilterExact) -> Result<Self, EntityError> {
688 self.filter = Some(filter.to_mongo_filter_exact()?);
689 Ok(self)
690 }
691
692 pub fn filter_many(mut self, filter: impl ToMongoFilterMany) -> Self {
693 self.filter = filter.to_mongo_filter_many();
694 self
695 }
696
697 pub fn filter(mut self, filter: Document) -> Self {
698 self.filter = Some(filter);
699 self
700 }
701
702 pub fn page_selector(
703 mut self,
704 page_selector: impl TryInto<PageInfo, Error = EntityError>,
705 ) -> Result<Self, EntityError> {
706 self.page = Some(page_selector.try_into()?);
707 Ok(self)
708 }
709
710 pub fn page(mut self, page: PageInfo) -> Self {
711 self.page = Some(page);
712 self
713 }
714
715 pub fn sort(mut self, sort: impl Into<Option<Document>>) -> Self {
716 self.sort = sort.into();
717 self
718 }
719}
720
721impl<'q, T, ID> IntoFuture for Query<'q, T, ID>
722where
723 T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin + 'q,
724 ID: DeserializeOwned + Serialize + Send + Sync + Unpin + 'q,
725{
726 type Output = Result<Page<EntityOwned<T, ID>>, EntityError>;
727
728 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'q>>;
729
730 fn into_future(self) -> Self::IntoFuture {
731 Box::pin(EntityOwned::<T, ID>::page_filter_sort(
732 self.db,
733 self.filter.unwrap_or_default(),
734 self.sort,
735 self.page.unwrap_or_default(),
736 ))
737 }
738}
739
740#[derive(Debug, Deserialize, Serialize)]
741pub struct Defaults {
742 pub created: UserModification,
743 pub modified: UserModification,
744}
745
746impl Defaults {
747 pub fn now(user_id: Uuid) -> Self {
748 let modify = UserModification::now(user_id);
749 Self {
750 created: modify.clone(),
751 modified: modify,
752 }
753 }
754
755 pub fn update_by(&self, user_id: Uuid) -> Self {
756 let modified = UserModification::now(user_id);
757 Self {
758 created: self.created.clone(),
759 modified,
760 }
761 }
762}
763
764#[derive(Debug, Clone, Deserialize, Serialize)]
765pub struct UserModification {
766 #[serde(rename = "uid")]
767 pub user_id: Uuid,
768 #[serde(with = "chrono_datetime_as_bson_datetime")]
769 pub at: DateTime<Utc>,
770}
771
772impl UserModification {
773 pub fn now(user_id: Uuid) -> Self {
774 Self {
775 user_id,
776 at: Utc::now(),
777 }
778 }
779}
780
781pub trait EntityField {
782 type Field<T: Serialize + DeserializeOwned>: Serialize + DeserializeOwned;
783}
784
785#[derive(Default, Clone, PartialEq, Debug)]
786pub struct Optional;
787impl EntityField for Optional {
788 type Field<T: Serialize + DeserializeOwned> = Option<T>;
789}
790
791#[derive(Default, Clone, PartialEq, Debug)]
792pub struct Required;
793impl EntityField for Required {
794 type Field<T: Serialize + DeserializeOwned> = T;
795}
796
797pub trait MongoCollection {
798 const COLLECTION: &'static str;
799
800 fn mongo_collection<T>(db: &Database) -> Collection<T>
801 where
802 T: Send + Sync,
803 {
804 db.collection(Self::COLLECTION)
805 }
806}