qm_entity/
lib.rs

1use async_graphql::{Context, ErrorExtensions, FieldResult};
2use error::EntityResult;
3use futures::stream::TryStreamExt;
4use serde::{de::DeserializeOwned, Serialize};
5
6use qm_mongodb::{
7    bson::{doc, oid::ObjectId, Document, Uuid},
8    options::FindOptions,
9    results::DeleteResult,
10};
11
12use crate::{
13    ids::ID,
14    model::{ListFilter, ListResult},
15};
16
17pub mod error;
18pub mod ids;
19pub mod list;
20pub mod model;
21pub mod owned;
22
23pub trait MutatePermissions {
24    fn create() -> Self;
25    fn update() -> Self;
26    fn delete() -> Self;
27}
28
29pub trait QueryPermissions {
30    fn list() -> Self;
31    fn view() -> Self;
32}
33
34pub fn conflict<E>(err: E) -> async_graphql::Error
35where
36    E: ErrorExtensions,
37{
38    err.extend_with(|_err, e| e.set("code", 409))
39}
40
41pub fn conflicting_name<T>(ty: &str, name: &str) -> Result<T, async_graphql::Error> {
42    Err(conflict(async_graphql::Error::new(format!(
43        "{ty} with the name '{name}' already exists."
44    ))))
45}
46
47pub fn unauthorized<E>(err: E) -> async_graphql::Error
48where
49    E: ErrorExtensions,
50{
51    err.extend_with(|_err, e| e.set("code", 401))
52}
53
54pub fn unauthorized_name<T>(ty: &str, name: &str) -> Result<T, async_graphql::Error> {
55    Err(unauthorized(async_graphql::Error::new(format!(
56        "{ty} '{name}' nicht authorisiert."
57    ))))
58}
59
60#[allow(async_fn_in_trait)]
61pub trait FromGraphQLContext: Sized {
62    async fn from_graphql_context(ctx: &Context<'_>) -> FieldResult<Self>;
63}
64
65pub trait IsAdmin {
66    fn is_admin(&self) -> bool {
67        false
68    }
69}
70
71pub trait IsSupport {
72    fn is_support(&self) -> bool {
73        false
74    }
75}
76
77pub trait HasAccess {
78    fn has_access(&self, a: &qm_role::Access) -> bool;
79}
80
81pub trait HasRole<R, P>
82where
83    R: std::fmt::Debug + std::marker::Copy + Clone,
84    P: std::fmt::Debug + std::marker::Copy + Clone,
85{
86    fn has_role(&self, r: &R, p: &P) -> bool;
87    fn has_role_object(&self, role: &qm_role::Role<R, P>) -> bool;
88}
89
90pub trait UserId {
91    fn user_id(&self) -> Option<&sqlx::types::Uuid>;
92}
93pub trait SessionAccess {
94    fn session_access(&self) -> Option<&qm_role::Access>;
95}
96
97pub trait AsNumber {
98    fn as_number(&self) -> u32;
99}
100
101pub struct Collection<T>(pub qm_mongodb::Collection<T>)
102where
103    T: Send + Sync;
104impl<T> AsRef<qm_mongodb::Collection<T>> for Collection<T>
105where
106    T: Send + Sync,
107{
108    fn as_ref(&self) -> &qm_mongodb::Collection<T> {
109        &self.0
110    }
111}
112
113impl<T> Collection<T>
114where
115    T: DeserializeOwned + Send + Sync + Unpin,
116{
117    pub async fn by_id(&self, id: &ObjectId) -> qm_mongodb::error::Result<Option<T>> {
118        self.as_ref().find_one(doc! { "_id": id }).await
119    }
120
121    pub async fn by_name(&self, name: &str) -> qm_mongodb::error::Result<Option<T>> {
122        self.as_ref().find_one(doc! { "name": name }).await
123    }
124
125    pub async fn by_field(&self, field: &str, value: &str) -> qm_mongodb::error::Result<Option<T>> {
126        self.as_ref().find_one(doc! { field: value }).await
127    }
128
129    pub async fn remove_all_by_strings(
130        &self,
131        field: &str,
132        values: &[String],
133    ) -> qm_mongodb::error::Result<DeleteResult> {
134        self.as_ref()
135            .delete_many(doc! { field: { "$in": values } })
136            .await
137    }
138
139    pub async fn remove_all_by_uuids(
140        &self,
141        field: &str,
142        values: &[&Uuid],
143    ) -> qm_mongodb::error::Result<DeleteResult> {
144        self.as_ref()
145            .delete_many(doc! { field: { "$in": values } })
146            .await
147    }
148
149    pub async fn by_field_with_customer_filter(
150        &self,
151        cid: &ObjectId,
152        field: &str,
153        value: &str,
154    ) -> qm_mongodb::error::Result<Option<T>> {
155        self.as_ref()
156            .find_one(doc! {
157                "owner.entityId.cid": &cid,
158                field: value
159            })
160            .await
161    }
162
163    pub async fn list(
164        &self,
165        query: Option<Document>,
166        filter: Option<ListFilter>,
167    ) -> qm_mongodb::error::Result<ListResult<T>> {
168        let query = query.unwrap_or_default();
169        let limit = filter
170            .as_ref()
171            .and_then(|filter| filter.limit.as_ref().copied())
172            .unwrap_or(1000) as i64;
173        let page = filter
174            .as_ref()
175            .and_then(|filter| filter.page.as_ref().copied())
176            .unwrap_or(0);
177        let offset = page as u64 * limit as u64;
178        let total = self.as_ref().count_documents(query.clone()).await?;
179        let options = FindOptions::builder().limit(limit).skip(offset).build();
180
181        let items = self
182            .as_ref()
183            .find(query)
184            .with_options(options)
185            .await?
186            .try_collect::<Vec<T>>()
187            .await?;
188        Ok(ListResult {
189            items,
190            limit: Some(limit),
191            total: Some(total as i64),
192            page: Some(page as i64),
193        })
194    }
195}
196
197impl<T> Collection<T>
198where
199    T: Serialize + Send + Sync + Unpin + AsMut<Option<ID>>,
200{
201    pub async fn save(&self, mut value: T) -> qm_mongodb::error::Result<T> {
202        let id: qm_mongodb::bson::Bson = self.as_ref().insert_one(&value).await?.inserted_id;
203        if let qm_mongodb::bson::Bson::ObjectId(cid) = id {
204            *value.as_mut() = Some(cid);
205        }
206        Ok(value)
207    }
208}
209
210pub trait Create<T, C: UserId> {
211    fn create(self, ctx: &C) -> EntityResult<T>;
212}
213
214#[doc(hidden)]
215pub mod __private {
216    pub use crate::error::EntityError;
217    #[doc(hidden)]
218    pub use core::result::Result::Err;
219}
220
221#[macro_export]
222macro_rules! err {
223    ($($arg:tt)*) => {
224        $crate::__private::Err($crate::__private::EntityError::$($arg)*)
225    };
226}
227#[macro_export]
228macro_rules! exerr {
229    ($($arg:tt)*) => {
230        $crate::__private::Err($crate::__private::EntityError::$($arg)*.extend())
231    };
232}