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    /// Updates an entity.
517    pub async fn update(
518        db: &Database,
519        context: impl ToMongoFilterOne,
520        input: impl UpdateEntity<T>,
521        user_id: Uuid,
522    ) -> Result<Self, EntityError>
523    where
524        T: Clone,
525    {
526        let filter = context.to_mongo_filter_one();
527        let Some(mut entity): Option<Self> =
528            T::mongo_collection(db).find_one(filter.clone()).await?
529        else {
530            return Err(EntityError::NotFound);
531        };
532
533        if let Cow::Owned(updated) = input.update_entity(&entity.fields)? {
534            entity.fields = updated;
535            entity.defaults = Arc::new(entity.defaults.update_by(user_id));
536
537            if let Some(filter) = filter.into() {
538                T::mongo_collection::<Self>(db)
539                    .replace_one(filter, &entity)
540                    .await?;
541            }
542        }
543
544        Ok(entity)
545    }
546
547    /// Saves an entity (insert or update).
548    pub async fn save<C>(
549        db: &Database,
550        context: C,
551        input: impl Into<T>,
552        user_id: Uuid,
553    ) -> Result<bool, EntityError>
554    where
555        T: Clone + std::fmt::Debug,
556        C: ToMongoFilterOne + Into<OwnerId>,
557    {
558        let filter = context.to_mongo_filter_one();
559        #[derive(Debug, Serialize)]
560        struct SaveEntity<F> {
561            owner: OwnerId,
562            #[serde(flatten)]
563            fields: F,
564            #[serde(flatten)]
565            defaults: Arc<Defaults>,
566        }
567        let defaults = Arc::new(Defaults::now(user_id));
568        let entity = SaveEntity {
569            owner: context.into(),
570            fields: input.into(),
571            defaults,
572        };
573        let result = T::mongo_collection::<SaveEntity<_>>(db)
574            .replace_one(filter, &entity)
575            .upsert(true)
576            .await?;
577        Ok(result.modified_count > 0 || result.upserted_id.is_some())
578    }
579
580    /// Saves an entity with a specific ID.
581    pub async fn save_with_id<C, I>(
582        db: &Database,
583        context: C,
584        input: I,
585        user_id: Uuid,
586    ) -> Result<Option<C>, EntityError>
587    where
588        T: Clone + std::fmt::Debug,
589        C: FromMongoId + IsMongoInsert + ToMongoFilterOne + Into<OwnerId> + Clone,
590        I: Into<T> + Send + Sync,
591    {
592        let filter = context.to_mongo_filter_one();
593        Ok(if context.is_mongo_insert() {
594            #[derive(Debug, Serialize)]
595            struct SaveEntity<F> {
596                owner: OwnerId,
597                #[serde(flatten)]
598                fields: F,
599                #[serde(flatten)]
600                defaults: Defaults,
601            }
602            let defaults = Defaults::now(user_id);
603            let entity = SaveEntity {
604                owner: context.clone().into(),
605                fields: input.into(),
606                defaults,
607            };
608            let result = T::mongo_collection::<SaveEntity<T>>(db)
609                .insert_one(&entity)
610                .await?;
611            C::from_mongo_id(context, result.inserted_id)
612        } else {
613            #[derive(Debug, Serialize)]
614            struct SaveEntity<F> {
615                owner: OwnerId,
616                #[serde(flatten)]
617                fields: F,
618                modified: UserModification,
619            }
620            let entity = SaveEntity {
621                owner: context.clone().into(),
622                fields: input.into(),
623                modified: UserModification::now(user_id),
624            };
625            let result = T::mongo_collection::<SaveEntity<T>>(db)
626                .update_one(filter, doc!{ "$set": serialize_to_bson(&entity).map_err(|err| EntityError::Bson(err.to_string()))? })
627                .await?;
628            if result.matched_count == 0 {
629                return Err(EntityError::NotFound);
630            }
631            if result.modified_count > 0 {
632                Some(context)
633            } else {
634                None
635            }
636        })
637    }
638
639    /// Removes entities by ID.
640    pub async fn remove<I>(db: &Database, ids: I) -> Result<i32, EntityError>
641    where
642        I: ToMongoFilterExact,
643    {
644        let result = T::mongo_collection::<Document>(db)
645            .delete_many(ids.to_mongo_filter_exact()?)
646            .await?;
647        Ok(result.deleted_count as i32)
648    }
649
650    /// Returns a paginated list of entities with the given filter.
651    pub async fn page_filter(
652        db: &Database,
653        filter: Document,
654        page_selector: impl TryInto<PageInfo, Error = EntityError>,
655    ) -> Result<Page<Self>, EntityError> {
656        let page_info: PageInfo = page_selector.try_into()?;
657        Self::page_filter_sort(db, filter, None, page_info).await
658    }
659
660    /// Returns a paginated list with sorting.
661    pub async fn page_filter_sort(
662        db: &Database,
663        filter: Document,
664        sort: Option<Document>,
665        page_info: PageInfo,
666    ) -> Result<Page<Self>, EntityError> {
667        let total = T::mongo_collection::<Self>(db)
668            .find(filter.clone())
669            .await?
670            .count()
671            .await;
672
673        let limit = page_info.limit;
674
675        if total == 0 {
676            return Ok(if limit.is_some() {
677                Page {
678                    limit,
679                    ..Page::empty()
680                }
681            } else {
682                Page::empty()
683            });
684        }
685
686        T::mongo_collection(db)
687            .find(filter)
688            .with_options(
689                FindOptions::builder()
690                    .limit(limit)
691                    .sort(sort)
692                    .skip(page_info.skip)
693                    .build(),
694            )
695            .await?
696            .try_collect()
697            .await
698            .map(|items| Page {
699                items,
700                total,
701                skip: page_info.skip.unwrap_or_default(),
702                limit,
703            })
704            .map_err(From::from)
705    }
706}
707
708/// Represents a query for a collection of entities.
709pub struct Query<'q, T, ID> {
710    db: &'q Database,
711    filter: Option<Document>,
712    page: Option<PageInfo>,
713    sort: Option<Document>,
714    marker: PhantomData<(T, ID)>,
715}
716
717impl<'q, T, ID> Query<'q, T, ID> {
718    /// Creates a new query.
719    pub fn new(db: &'q Database) -> Self {
720        Self {
721            db,
722            filter: None,
723            page: None,
724            sort: None,
725            marker: Default::default(),
726        }
727    }
728
729    /// Adds an exact filter to the query.
730    pub fn filter_exact(mut self, filter: impl ToMongoFilterExact) -> Result<Self, EntityError> {
731        self.filter = Some(filter.to_mongo_filter_exact()?);
732        Ok(self)
733    }
734
735    /// Adds a "many" filter to the query.
736    pub fn filter_many(mut self, filter: impl ToMongoFilterMany) -> Self {
737        self.filter = filter.to_mongo_filter_many();
738        self
739    }
740
741    /// Adds a document filter to the query.
742    pub fn filter(mut self, filter: Document) -> Self {
743        self.filter = Some(filter);
744        self
745    }
746
747    /// Adds a page selector to the query.
748    pub fn page_selector(
749        mut self,
750        page_selector: impl TryInto<PageInfo, Error = EntityError>,
751    ) -> Result<Self, EntityError> {
752        self.page = Some(page_selector.try_into()?);
753        Ok(self)
754    }
755
756    /// Adds a page to the query.
757    pub fn page(mut self, page: PageInfo) -> Self {
758        self.page = Some(page);
759        self
760    }
761
762    /// Adds sorting to the query.
763    pub fn sort(mut self, sort: impl Into<Option<Document>>) -> Self {
764        self.sort = sort.into();
765        self
766    }
767}
768
769impl<'q, T, ID> IntoFuture for Query<'q, T, ID>
770where
771    T: DeserializeOwned + Serialize + MongoCollection + Send + Sync + Unpin + 'q,
772    ID: DeserializeOwned + Serialize + Send + Sync + Unpin + 'q,
773{
774    type Output = Result<Page<EntityOwned<T, ID>>, EntityError>;
775
776    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + 'q>>;
777
778    fn into_future(self) -> Self::IntoFuture {
779        Box::pin(EntityOwned::<T, ID>::page_filter_sort(
780            self.db,
781            self.filter.unwrap_or_default(),
782            self.sort,
783            self.page.unwrap_or_default(),
784        ))
785    }
786}
787
788/// Default values for entity creation and modification.
789#[derive(Debug, Deserialize, Serialize)]
790pub struct Defaults {
791    /// Creation timestamp and user.
792    pub created: UserModification,
793    /// Last modification timestamp and user.
794    pub modified: UserModification,
795}
796
797impl Defaults {
798    /// Creates new defaults with the given user ID.
799    pub fn now(user_id: Uuid) -> Self {
800        let modify = UserModification::now(user_id);
801        Self {
802            created: modify.clone(),
803            modified: modify,
804        }
805    }
806
807    /// Updates the modification time with the given user ID.
808    pub fn update_by(&self, user_id: Uuid) -> Self {
809        let modified = UserModification::now(user_id);
810        Self {
811            created: self.created.clone(),
812            modified,
813        }
814    }
815}
816
817/// User modification tracking.
818#[derive(Debug, Clone, Deserialize, Serialize)]
819pub struct UserModification {
820    /// User ID.
821    #[serde(rename = "uid")]
822    pub user_id: Uuid,
823    /// Timestamp of modification.
824    #[serde(with = "FromChrono04DateTime")]
825    pub at: DateTime<Utc>,
826}
827
828impl UserModification {
829    /// Creates a new modification with the current time.
830    pub fn now(user_id: Uuid) -> Self {
831        Self {
832            user_id,
833            at: Utc::now(),
834        }
835    }
836}
837
838/// Trait for entity field types.
839pub trait EntityField {
840    /// The field type.
841    type Field<T: Serialize + DeserializeOwned>: Serialize + DeserializeOwned;
842}
843
844/// Optional field marker.
845#[derive(Default, Clone, PartialEq, Debug)]
846pub struct Optional;
847impl EntityField for Optional {
848    type Field<T: Serialize + DeserializeOwned> = Option<T>;
849}
850
851/// Required field marker.
852#[derive(Default, Clone, PartialEq, Debug)]
853pub struct Required;
854impl EntityField for Required {
855    type Field<T: Serialize + DeserializeOwned> = T;
856}
857
858/// Trait for MongoDB collection access.
859pub trait MongoCollection {
860    /// Collection name.
861    const COLLECTION: &'static str;
862
863    /// Gets the MongoDB collection.
864    fn mongo_collection<T>(db: &Database) -> Collection<T>
865    where
866        T: Send + Sync,
867    {
868        db.collection(Self::COLLECTION)
869    }
870}