Skip to main content

qm_entity/
owned.rs

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/// External representation of Object ID type in MongoDB.
38#[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/// Entity Id.
75#[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
117/// Trait for converting to a MongoDB filter for many documents.
118pub trait ToMongoFilterMany {
119    /// Converts to a MongoDB filter document.
120    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
183/// Trait for converting to a MongoDB filter for one document.
184pub trait ToMongoFilterOne {
185    /// Converts to a MongoDB filter document.
186    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
251/// Trait for converting to an exact MongoDB filter.
252pub trait ToMongoFilterExact {
253    /// Converts to an exact MongoDB filter.
254    fn to_mongo_filter_exact(&self) -> Result<Document, EntityError>;
255}
256
257/// Filter for multiple resources.
258pub 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
279/// Trait for converting to MongoDB ObjectId.
280pub trait AsMongoId {
281    /// Returns the MongoDB ObjectId.
282    fn as_mongo_id(&self) -> ObjectId;
283}
284
285/// Trait for creating from MongoDB Bson.
286pub trait FromMongoId: Sized {
287    /// Creates from a MongoDB Bson value.
288    fn from_mongo_id(old_id: Self, bson: Bson) -> Option<Self>;
289}
290
291/// Trait for checking if this is a MongoDB insert operation.
292pub trait IsMongoInsert {
293    /// Returns true if this is an insert operation.
294    fn is_mongo_insert(&self) -> bool;
295}
296
297/// Generic entity wrapper.
298#[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/// Pagination result.
308#[derive(Debug, Default, Deserialize, Serialize)]
309pub struct Page<I> {
310    /// Items in the page.
311    pub items: Vec<I>,
312    /// Number of items skipped.
313    pub skip: u64,
314    /// Page limit.
315    pub limit: Option<i64>,
316    /// Total items count.
317    pub total: usize,
318}
319
320impl<I> Page<I> {
321    /// Empty page.
322    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    /// Returns page index.
332    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    /// Returns page count.
341    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)]
351/// Pagination info for queries.
352pub struct PageInfo {
353    /// Number of items to skip.
354    skip: Option<u64>,
355    /// Maximum items to return.
356    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
381/// Trait for updating entity fields.
382pub trait UpdateEntity<T: Clone> {
383    /// Updates the entity with new fields.
384    fn update_entity(self, entity: &T) -> Result<Cow<'_, T>, EntityError>;
385}
386
387/// Owned entity with ID and owner.
388#[derive(Debug, Deserialize, Serialize, Clone)]
389pub struct EntityOwned<T, ID = Id> {
390    /// The entity ID.
391    #[serde(rename = "_id")]
392    pub id: ID,
393    /// Owner information.
394    pub owner: Arc<OwnerId>,
395    /// Entity fields.
396    #[serde(flatten)]
397    pub fields: T,
398    /// Default values.
399    #[serde(flatten)]
400    pub defaults: Arc<Defaults>,
401}
402
403impl<T> EntityOwned<T>
404where
405    T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin,
406{
407    /// Creates a new entity.
408    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    /// Query owned entities.
452    pub fn query(db: &Database) -> Query<'_, T, ID> {
453        Query::new(db)
454    }
455
456    /// Lists entities matching the filter.
457    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    /// Returns a paginated list of entities.
470    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    /// Lists entities with exact filter matching.
484    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    /// Returns a paginated list with exact filter matching.
497    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    /// Gets an entity by ID.
506    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    /// Gets entities by IDs.
517    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    /// Updates an entity.
530    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    /// Saves an entity (insert or update).
561    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    /// Saves an entity with a specific ID.
594    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    /// Removes entities by ID.
653    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    /// Returns a paginated list of entities with the given filter.
664    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    /// Returns a paginated list with sorting.
674    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
721/// Represents a query for a collection of entities.
722pub 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    /// Creates a new query.
732    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    /// Adds an exact filter to the query.
743    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    /// Adds a "many" filter to the query.
749    pub fn filter_many(mut self, filter: impl ToMongoFilterMany) -> Self {
750        self.filter = filter.to_mongo_filter_many();
751        self
752    }
753
754    /// Adds a document filter to the query.
755    pub fn filter(mut self, filter: Document) -> Self {
756        self.filter = Some(filter);
757        self
758    }
759
760    /// Adds a page selector to the query.
761    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    /// Adds a page to the query.
770    pub fn page(mut self, page: PageInfo) -> Self {
771        self.page = Some(page);
772        self
773    }
774
775    /// Adds sorting to the query.
776    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/// Default values for entity creation and modification.
802#[derive(Debug, Deserialize, Serialize)]
803pub struct Defaults {
804    /// Creation timestamp and user.
805    pub created: UserModification,
806    /// Last modification timestamp and user.
807    pub modified: UserModification,
808}
809
810impl Defaults {
811    /// Creates new defaults with the given user ID.
812    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    /// Updates the modification time with the given user ID.
821    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/// User modification tracking.
831#[derive(Debug, Clone, Deserialize, Serialize)]
832pub struct UserModification {
833    /// User ID.
834    #[serde(rename = "uid")]
835    pub user_id: Uuid,
836    /// Timestamp of modification.
837    #[serde(with = "FromChrono04DateTime")]
838    pub at: DateTime<Utc>,
839}
840
841impl UserModification {
842    /// Creates a new modification with the current time.
843    pub fn now(user_id: Uuid) -> Self {
844        Self {
845            user_id,
846            at: Utc::now(),
847        }
848    }
849}
850
851/// Trait for entity field types.
852pub trait EntityField {
853    /// The field type.
854    type Field<T: Serialize + DeserializeOwned>: Serialize + DeserializeOwned;
855}
856
857/// Optional field marker.
858#[derive(Default, Clone, PartialEq, Debug)]
859pub struct Optional;
860impl EntityField for Optional {
861    type Field<T: Serialize + DeserializeOwned> = Option<T>;
862}
863
864/// Required field marker.
865#[derive(Default, Clone, PartialEq, Debug)]
866pub struct Required;
867impl EntityField for Required {
868    type Field<T: Serialize + DeserializeOwned> = T;
869}
870
871/// Trait for MongoDB collection access.
872pub trait MongoCollection {
873    /// Collection name.
874    const COLLECTION: &'static str;
875
876    /// Gets the MongoDB collection.
877    fn mongo_collection<T>(db: &Database) -> Collection<T>
878    where
879        T: Send + Sync,
880    {
881        db.collection(Self::COLLECTION)
882    }
883}