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}