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::datetime::FromChrono04DateTime, serialize_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 {
119 fn to_mongo_filter_many(&self) -> Option<Document>;
121}
122
123impl ToMongoFilterMany for () {
124 fn to_mongo_filter_many(&self) -> Option<Document> {
125 None
126 }
127}
128
129impl ToMongoFilterMany for Option<Document> {
130 fn to_mongo_filter_many(&self) -> Option<Document> {
131 self.clone()
132 }
133}
134
135impl<T> ToMongoFilterMany for Option<T>
136where
137 T: ToMongoFilterMany,
138{
139 fn to_mongo_filter_many(&self) -> Option<Document> {
140 self.as_ref().and_then(|v| v.to_mongo_filter_many())
141 }
142}
143
144impl ToMongoFilterMany for CustomerId {
145 fn to_mongo_filter_many(&self) -> Option<Document> {
146 let cid = self.unzip();
147 Some(doc! { "owner.cid": cid })
148 }
149}
150
151impl ToMongoFilterMany for OrganizationId {
152 fn to_mongo_filter_many(&self) -> Option<Document> {
153 let (cid, oid) = self.unzip();
154 Some(doc! { "owner.cid": cid, "owner.oid": oid })
155 }
156}
157
158impl ToMongoFilterMany for InstitutionId {
159 fn to_mongo_filter_many(&self) -> Option<Document> {
160 let (cid, oid, iid) = self.unzip();
161 Some(doc! { "owner.cid": cid, "owner.oid": oid, "owner.iid": iid })
162 }
163}
164
165impl ToMongoFilterMany for CustomerOrOrganization {
166 fn to_mongo_filter_many(&self) -> Option<Document> {
167 match self {
168 Self::Customer(v) => v.to_mongo_filter_many(),
169 Self::Organization(v) => v.to_mongo_filter_many(),
170 }
171 }
172}
173
174impl ToMongoFilterMany for OrganizationOrInstitution {
175 fn to_mongo_filter_many(&self) -> Option<Document> {
176 match self {
177 Self::Institution(v) => v.to_mongo_filter_many(),
178 Self::Organization(v) => v.to_mongo_filter_many(),
179 }
180 }
181}
182
183pub trait ToMongoFilterOne {
185 fn to_mongo_filter_one(&self) -> Document;
187}
188
189impl ToMongoFilterOne for Document {
190 fn to_mongo_filter_one(&self) -> Document {
191 self.clone()
192 }
193}
194
195impl ToMongoFilterOne for CustomerResourceId {
196 fn to_mongo_filter_one(&self) -> Document {
197 let (.., id) = self.unzip();
198 doc! { "_id": id }
199 }
200}
201
202impl ToMongoFilterOne for OrganizationResourceId {
203 fn to_mongo_filter_one(&self) -> Document {
204 doc! { "_id": self.id() }
205 }
206}
207
208impl ToMongoFilterOne for InstitutionResourceId {
209 fn to_mongo_filter_one(&self) -> Document {
210 let (.., id) = self.unzip();
211 doc! { "_id": id }
212 }
213}
214
215impl ToMongoFilterOne for CustomerId {
216 fn to_mongo_filter_one(&self) -> Document {
217 doc! { "_id": self.unzip() }
218 }
219}
220
221impl ToMongoFilterOne for OrganizationId {
222 fn to_mongo_filter_one(&self) -> Document {
223 doc! { "_id": self.id() }
224 }
225}
226
227impl ToMongoFilterOne for InstitutionId {
228 fn to_mongo_filter_one(&self) -> Document {
229 doc! { "_id": self.id() }
230 }
231}
232
233impl ToMongoFilterOne for CustomerOrOrganization {
234 fn to_mongo_filter_one(&self) -> Document {
235 match self {
236 Self::Customer(v) => v.to_mongo_filter_one(),
237 Self::Organization(v) => v.to_mongo_filter_one(),
238 }
239 }
240}
241
242impl ToMongoFilterOne for OrganizationOrInstitution {
243 fn to_mongo_filter_one(&self) -> Document {
244 match self {
245 Self::Institution(v) => v.to_mongo_filter_one(),
246 Self::Organization(v) => v.to_mongo_filter_one(),
247 }
248 }
249}
250
251pub trait ToMongoFilterExact {
253 fn to_mongo_filter_exact(&self) -> Result<Document, EntityError>;
255}
256
257pub struct ResourcesFilter<'a, I>(pub &'a [I])
259where
260 I: ToMongoFilterOne;
261impl<I> ToMongoFilterExact for ResourcesFilter<'_, I>
262where
263 I: ToMongoFilterOne,
264{
265 fn to_mongo_filter_exact(&self) -> Result<Document, EntityError> {
266 if self.0.is_empty() {
267 return Err(EntityError::NotEmpty);
268 }
269 if self.0.len() == 1 {
270 return Ok(self.0.first().unwrap().to_mongo_filter_one());
271 }
272 let items: Vec<Document> = self.0.iter().map(|v| v.to_mongo_filter_one()).collect();
273 Ok(doc! {
274 "$or": items,
275 })
276 }
277}
278
279pub trait AsMongoId {
281 fn as_mongo_id(&self) -> ObjectId;
283}
284
285pub trait FromMongoId: Sized {
287 fn from_mongo_id(old_id: Self, bson: Bson) -> Option<Self>;
289}
290
291pub trait IsMongoInsert {
293 fn is_mongo_insert(&self) -> bool;
295}
296
297#[derive(Debug, Deserialize, Serialize)]
299pub struct Entity<T> {
300 id: ID,
301 #[serde(flatten)]
302 fields: T,
303 #[serde(flatten)]
304 defaults: Defaults,
305}
306
307#[derive(Debug, Default, Deserialize, Serialize)]
309pub struct Page<I> {
310 pub items: Vec<I>,
312 pub skip: u64,
314 pub limit: Option<i64>,
316 pub total: usize,
318}
319
320impl<I> Page<I> {
321 pub fn empty() -> Self {
323 Self {
324 items: vec![],
325 total: 0,
326 skip: 0,
327 limit: Some(DEFAULT_PAGE_LIMIT),
328 }
329 }
330
331 pub fn index(&self) -> u64 {
333 if let Some(limit) = self.limit.filter(|l| *l > 0).map(|l| l as u64) {
334 self.skip / limit
335 } else {
336 0
337 }
338 }
339
340 pub fn count(&self) -> usize {
342 if let Some(limit) = self.limit.filter(|l| *l > 0).map(|l| l as usize) {
343 self.total.div_ceil(limit)
344 } else {
345 0
346 }
347 }
348}
349
350#[derive(Default)]
351pub struct PageInfo {
353 skip: Option<u64>,
355 limit: Option<i64>,
357}
358
359impl TryFrom<ListFilter> for PageInfo {
360 type Error = EntityError;
361
362 fn try_from(value: ListFilter) -> Result<Self, Self::Error> {
363 let limit = value.limit.map(|l| l as i64).unwrap_or(DEFAULT_PAGE_LIMIT);
364 Ok(Self {
365 skip: value.page.map(|page| limit as u64 * page as u64),
366 limit: Some(limit),
367 })
368 }
369}
370
371impl TryFrom<Option<ListFilter>> for PageInfo {
372 type Error = EntityError;
373
374 fn try_from(value: Option<ListFilter>) -> Result<Self, Self::Error> {
375 value
376 .map(|v| v.try_into())
377 .unwrap_or_else(|| Ok(Default::default()))
378 }
379}
380
381pub trait UpdateEntity<T: Clone> {
383 fn update_entity(self, entity: &T) -> Result<Cow<'_, T>, EntityError>;
385}
386
387#[derive(Debug, Deserialize, Serialize, Clone)]
389pub struct EntityOwned<T, ID = Id> {
390 #[serde(rename = "_id")]
392 pub id: ID,
393 pub owner: Arc<OwnerId>,
395 #[serde(flatten)]
397 pub fields: T,
398 #[serde(flatten)]
400 pub defaults: Arc<Defaults>,
401}
402
403impl<T> EntityOwned<T>
404where
405 T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
406{
407 pub async fn create(
409 db: &Database,
410 owner: impl Into<OwnerId>,
411 fields: T,
412 user_id: Uuid,
413 ) -> Result<Self, EntityError> {
414 #[derive(Serialize)]
415 struct CreateOwnedEntity<'f, F> {
416 owner: Arc<OwnerId>,
417 #[serde(flatten)]
418 fields: &'f F,
419 #[serde(flatten)]
420 defaults: Arc<Defaults>,
421 }
422
423 let owner = Arc::new(owner.into());
424 let defaults = Arc::new(Defaults::now(user_id));
425
426 T::mongo_collection(db)
427 .insert_one(CreateOwnedEntity {
428 owner: owner.clone(),
429 fields: &fields,
430 defaults: defaults.clone(),
431 })
432 .await?
433 .inserted_id
434 .as_object_id()
435 .map(Id)
436 .ok_or(EntityError::NoId)
437 .map(|id| Self {
438 id,
439 owner,
440 fields,
441 defaults,
442 })
443 }
444}
445
446impl<T, ID> EntityOwned<T, ID>
447where
448 T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
449 ID: DeserializeOwned + Serialize + Send + Sync + Unpin,
450{
451 pub fn query(db: &Database) -> Query<'_, T, ID> {
453 Query::new(db)
454 }
455
456 pub async fn list(
458 db: &Database,
459 filter: impl ToMongoFilterMany,
460 ) -> Result<Vec<Self>, EntityError> {
461 T::mongo_collection(db)
462 .find(filter.to_mongo_filter_many().unwrap_or_default())
463 .await?
464 .try_collect()
465 .await
466 .map_err(From::from)
467 }
468
469 pub async fn page(
471 db: &Database,
472 filter: impl ToMongoFilterMany,
473 page_selector: impl TryInto<PageInfo, Error = EntityError>,
474 ) -> Result<Page<Self>, EntityError> {
475 Self::page_filter(
476 db,
477 filter.to_mongo_filter_many().unwrap_or_default(),
478 page_selector,
479 )
480 .await
481 }
482
483 pub async fn list_exact(
485 db: &Database,
486 filter: impl ToMongoFilterExact,
487 ) -> Result<Vec<Self>, EntityError> {
488 T::mongo_collection(db)
489 .find(filter.to_mongo_filter_exact()?)
490 .await?
491 .try_collect()
492 .await
493 .map_err(From::from)
494 }
495
496 pub async fn page_exact(
498 db: &Database,
499 filter: impl ToMongoFilterExact,
500 page_selector: impl TryInto<PageInfo, Error = EntityError>,
501 ) -> Result<Page<Self>, EntityError> {
502 Self::page_filter(db, filter.to_mongo_filter_exact()?, page_selector).await
503 }
504
505 pub async fn by_id(
507 db: &Database,
508 id: impl ToMongoFilterOne,
509 ) -> Result<Option<Self>, EntityError> {
510 T::mongo_collection(db)
511 .find_one(id.to_mongo_filter_one())
512 .await
513 .map_err(From::from)
514 }
515
516 pub async fn by_ids(
518 db: &Database,
519 ids: impl ToMongoFilterMany,
520 ) -> Result<Vec<Self>, EntityError> {
521 T::mongo_collection(db)
522 .find(ids.to_mongo_filter_many().unwrap_or_default())
523 .await?
524 .try_collect()
525 .await
526 .map_err(From::from)
527 }
528
529 pub async fn update(
531 db: &Database,
532 context: impl ToMongoFilterOne,
533 input: impl UpdateEntity<T>,
534 user_id: Uuid,
535 ) -> Result<Self, EntityError>
536 where
537 T: Clone,
538 {
539 let filter = context.to_mongo_filter_one();
540 let Some(mut entity): Option<Self> =
541 T::mongo_collection(db).find_one(filter.clone()).await?
542 else {
543 return Err(EntityError::NotFound);
544 };
545
546 if let Cow::Owned(updated) = input.update_entity(&entity.fields)? {
547 entity.fields = updated;
548 entity.defaults = Arc::new(entity.defaults.update_by(user_id));
549
550 if let Some(filter) = filter.into() {
551 T::mongo_collection::<Self>(db)
552 .replace_one(filter, &entity)
553 .await?;
554 }
555 }
556
557 Ok(entity)
558 }
559
560 pub async fn save<C>(
562 db: &Database,
563 context: C,
564 input: impl Into<T>,
565 user_id: Uuid,
566 ) -> Result<bool, EntityError>
567 where
568 T: Clone + std::fmt::Debug,
569 C: ToMongoFilterOne + Into<OwnerId>,
570 {
571 let filter = context.to_mongo_filter_one();
572 #[derive(Debug, Serialize)]
573 struct SaveEntity<F> {
574 owner: OwnerId,
575 #[serde(flatten)]
576 fields: F,
577 #[serde(flatten)]
578 defaults: Arc<Defaults>,
579 }
580 let defaults = Arc::new(Defaults::now(user_id));
581 let entity = SaveEntity {
582 owner: context.into(),
583 fields: input.into(),
584 defaults,
585 };
586 let result = T::mongo_collection::<SaveEntity<_>>(db)
587 .replace_one(filter, &entity)
588 .upsert(true)
589 .await?;
590 Ok(result.modified_count > 0 || result.upserted_id.is_some())
591 }
592
593 pub async fn save_with_id<C, I>(
595 db: &Database,
596 context: C,
597 input: I,
598 user_id: Uuid,
599 ) -> Result<Option<C>, EntityError>
600 where
601 T: Clone + std::fmt::Debug,
602 C: FromMongoId + IsMongoInsert + ToMongoFilterOne + Into<OwnerId> + Clone,
603 I: Into<T> + Send + Sync,
604 {
605 let filter = context.to_mongo_filter_one();
606 Ok(if context.is_mongo_insert() {
607 #[derive(Debug, Serialize)]
608 struct SaveEntity<F> {
609 owner: OwnerId,
610 #[serde(flatten)]
611 fields: F,
612 #[serde(flatten)]
613 defaults: Defaults,
614 }
615 let defaults = Defaults::now(user_id);
616 let entity = SaveEntity {
617 owner: context.clone().into(),
618 fields: input.into(),
619 defaults,
620 };
621 let result = T::mongo_collection::<SaveEntity<T>>(db)
622 .insert_one(&entity)
623 .await?;
624 C::from_mongo_id(context, result.inserted_id)
625 } else {
626 #[derive(Debug, Serialize)]
627 struct SaveEntity<F> {
628 owner: OwnerId,
629 #[serde(flatten)]
630 fields: F,
631 modified: UserModification,
632 }
633 let entity = SaveEntity {
634 owner: context.clone().into(),
635 fields: input.into(),
636 modified: UserModification::now(user_id),
637 };
638 let result = T::mongo_collection::<SaveEntity<T>>(db)
639 .update_one(filter, doc!{ "$set": serialize_to_bson(&entity).map_err(|err| EntityError::Bson(err.to_string()))? })
640 .await?;
641 if result.matched_count == 0 {
642 return Err(EntityError::NotFound);
643 }
644 if result.modified_count > 0 {
645 Some(context)
646 } else {
647 None
648 }
649 })
650 }
651
652 pub async fn remove<I>(db: &Database, ids: I) -> Result<i32, EntityError>
654 where
655 I: ToMongoFilterExact,
656 {
657 let result = T::mongo_collection::<Document>(db)
658 .delete_many(ids.to_mongo_filter_exact()?)
659 .await?;
660 Ok(result.deleted_count as i32)
661 }
662
663 pub async fn page_filter(
665 db: &Database,
666 filter: Document,
667 page_selector: impl TryInto<PageInfo, Error = EntityError>,
668 ) -> Result<Page<Self>, EntityError> {
669 let page_info: PageInfo = page_selector.try_into()?;
670 Self::page_filter_sort(db, filter, None, page_info).await
671 }
672
673 pub async fn page_filter_sort(
675 db: &Database,
676 filter: Document,
677 sort: Option<Document>,
678 page_info: PageInfo,
679 ) -> Result<Page<Self>, EntityError> {
680 let total = T::mongo_collection::<Self>(db)
681 .find(filter.clone())
682 .await?
683 .count()
684 .await;
685
686 let limit = page_info.limit;
687
688 if total == 0 {
689 return Ok(if limit.is_some() {
690 Page {
691 limit,
692 ..Page::empty()
693 }
694 } else {
695 Page::empty()
696 });
697 }
698
699 T::mongo_collection(db)
700 .find(filter)
701 .with_options(
702 FindOptions::builder()
703 .limit(limit)
704 .sort(sort)
705 .skip(page_info.skip)
706 .build(),
707 )
708 .await?
709 .try_collect()
710 .await
711 .map(|items| Page {
712 items,
713 total,
714 skip: page_info.skip.unwrap_or_default(),
715 limit,
716 })
717 .map_err(From::from)
718 }
719}
720
721pub struct Query<'q, T, ID> {
723 db: &'q Database,
724 filter: Option<Document>,
725 page: Option<PageInfo>,
726 sort: Option<Document>,
727 marker: PhantomData<(T, ID)>,
728}
729
730impl<'q, T, ID> Query<'q, T, ID> {
731 pub fn new(db: &'q Database) -> Self {
733 Self {
734 db,
735 filter: None,
736 page: None,
737 sort: None,
738 marker: Default::default(),
739 }
740 }
741
742 pub fn filter_exact(mut self, filter: impl ToMongoFilterExact) -> Result<Self, EntityError> {
744 self.filter = Some(filter.to_mongo_filter_exact()?);
745 Ok(self)
746 }
747
748 pub fn filter_many(mut self, filter: impl ToMongoFilterMany) -> Self {
750 self.filter = filter.to_mongo_filter_many();
751 self
752 }
753
754 pub fn filter(mut self, filter: Document) -> Self {
756 self.filter = Some(filter);
757 self
758 }
759
760 pub fn page_selector(
762 mut self,
763 page_selector: impl TryInto<PageInfo, Error = EntityError>,
764 ) -> Result<Self, EntityError> {
765 self.page = Some(page_selector.try_into()?);
766 Ok(self)
767 }
768
769 pub fn page(mut self, page: PageInfo) -> Self {
771 self.page = Some(page);
772 self
773 }
774
775 pub fn sort(mut self, sort: impl Into<Option<Document>>) -> Self {
777 self.sort = sort.into();
778 self
779 }
780}
781
782impl<'q, T, ID> IntoFuture for Query<'q, T, ID>
783where
784 T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin + 'q,
785 ID: DeserializeOwned + Serialize + Send + Sync + Unpin + 'q,
786{
787 type Output = Result<Page<EntityOwned<T, ID>>, EntityError>;
788
789 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'q>>;
790
791 fn into_future(self) -> Self::IntoFuture {
792 Box::pin(EntityOwned::<T, ID>::page_filter_sort(
793 self.db,
794 self.filter.unwrap_or_default(),
795 self.sort,
796 self.page.unwrap_or_default(),
797 ))
798 }
799}
800
801#[derive(Debug, Deserialize, Serialize)]
803pub struct Defaults {
804 pub created: UserModification,
806 pub modified: UserModification,
808}
809
810impl Defaults {
811 pub fn now(user_id: Uuid) -> Self {
813 let modify = UserModification::now(user_id);
814 Self {
815 created: modify.clone(),
816 modified: modify,
817 }
818 }
819
820 pub fn update_by(&self, user_id: Uuid) -> Self {
822 let modified = UserModification::now(user_id);
823 Self {
824 created: self.created.clone(),
825 modified,
826 }
827 }
828}
829
830#[derive(Debug, Clone, Deserialize, Serialize)]
832pub struct UserModification {
833 #[serde(rename = "uid")]
835 pub user_id: Uuid,
836 #[serde(with = "FromChrono04DateTime")]
838 pub at: DateTime<Utc>,
839}
840
841impl UserModification {
842 pub fn now(user_id: Uuid) -> Self {
844 Self {
845 user_id,
846 at: Utc::now(),
847 }
848 }
849}
850
851pub trait EntityField {
853 type Field<T: Serialize + DeserializeOwned>: Serialize + DeserializeOwned;
855}
856
857#[derive(Default, Clone, PartialEq, Debug)]
859pub struct Optional;
860impl EntityField for Optional {
861 type Field<T: Serialize + DeserializeOwned> = Option<T>;
862}
863
864#[derive(Default, Clone, PartialEq, Debug)]
866pub struct Required;
867impl EntityField for Required {
868 type Field<T: Serialize + DeserializeOwned> = T;
869}
870
871pub trait MongoCollection {
873 const COLLECTION: &'static str;
875
876 fn mongo_collection<T>(db: &Database) -> Collection<T>
878 where
879 T: Send + Sync,
880 {
881 db.collection(Self::COLLECTION)
882 }
883}