1#![deny(missing_docs)]
2
3use async_graphql::{Context, ErrorExtensions, FieldResult};
50use error::EntityResult;
51use futures::stream::TryStreamExt;
52use serde::{de::DeserializeOwned, Serialize};
53
54use qm_mongodb::{
55 bson::{doc, oid::ObjectId, Document, Uuid},
56 options::FindOptions,
57 results::DeleteResult,
58};
59
60use crate::{
61 ids::ID,
62 model::{ListFilter, ListResult},
63};
64
65pub mod error;
67pub mod ids;
69pub mod list;
71pub mod model;
73pub mod owned;
75
76pub trait MutatePermissions {
80 fn create() -> Self;
82 fn update() -> Self;
84 fn delete() -> Self;
86}
87
88pub trait QueryPermissions {
92 fn list() -> Self;
94 fn view() -> Self;
96}
97
98pub fn conflict<E>(err: E) -> async_graphql::Error
102where
103 E: ErrorExtensions,
104{
105 err.extend_with(|_err, e| e.set("code", 409))
106}
107
108pub fn conflicting_name<T>(ty: &str, name: &str) -> Result<T, async_graphql::Error> {
110 Err(conflict(async_graphql::Error::new(format!(
111 "{ty} with the name '{name}' already exists."
112 ))))
113}
114
115pub fn unauthorized<E>(err: E) -> async_graphql::Error
119where
120 E: ErrorExtensions,
121{
122 err.extend_with(|_err, e| e.set("code", 401))
123}
124
125pub fn unauthorized_name<T>(ty: &str, name: &str) -> Result<T, async_graphql::Error> {
127 Err(unauthorized(async_graphql::Error::new(format!(
128 "{ty} '{name}' nicht authorisiert."
129 ))))
130}
131
132#[allow(async_fn_in_trait)]
133pub trait FromGraphQLContext: Sized {
137 async fn from_graphql_context(ctx: &Context<'_>) -> FieldResult<Self>;
139}
140
141pub trait IsAdmin {
143 fn is_admin(&self) -> bool {
145 false
146 }
147}
148
149pub trait IsSupport {
151 fn is_support(&self) -> bool {
153 false
154 }
155}
156
157pub trait HasAccess {
159 fn has_access(&self, a: &qm_role::Access) -> bool;
161}
162
163pub trait HasRole<R, P>
167where
168 R: std::fmt::Debug + std::marker::Copy + Clone,
169 P: std::fmt::Debug + std::marker::Copy + Clone,
170{
171 fn has_role(&self, r: &R, p: &P) -> bool;
173 fn has_role_object(&self, role: &qm_role::Role<R, P>) -> bool;
175}
176
177pub trait UserId {
179 fn user_id(&self) -> Option<&sqlx::types::Uuid>;
181}
182
183pub trait SessionAccess {
185 fn session_access(&self) -> Option<&qm_role::Access>;
187}
188
189pub trait AsNumber {
191 fn as_number(&self) -> u32;
193}
194
195pub struct Collection<T>(pub qm_mongodb::Collection<T>)
200where
201 T: Send + Sync;
202
203impl<T> AsRef<qm_mongodb::Collection<T>> for Collection<T>
204where
205 T: Send + Sync,
206{
207 fn as_ref(&self) -> &qm_mongodb::Collection<T> {
208 &self.0
209 }
210}
211
212impl<T> Collection<T>
213where
214 T: DeserializeOwned + Send + Sync + Unpin,
215{
216 pub async fn by_id(&self, id: &ObjectId) -> qm_mongodb::error::Result<Option<T>> {
218 self.as_ref().find_one(doc! { "_id": id }).await
219 }
220
221 pub async fn by_name(&self, name: &str) -> qm_mongodb::error::Result<Option<T>> {
223 self.as_ref().find_one(doc! { "name": name }).await
224 }
225
226 pub async fn by_field(&self, field: &str, value: &str) -> qm_mongodb::error::Result<Option<T>> {
228 self.as_ref().find_one(doc! { field: value }).await
229 }
230
231 pub async fn remove_all_by_strings(
233 &self,
234 field: &str,
235 values: &[String],
236 ) -> qm_mongodb::error::Result<DeleteResult> {
237 self.as_ref()
238 .delete_many(doc! { field: { "$in": values } })
239 .await
240 }
241
242 pub async fn remove_all_by_uuids(
244 &self,
245 field: &str,
246 values: &[&Uuid],
247 ) -> qm_mongodb::error::Result<DeleteResult> {
248 self.as_ref()
249 .delete_many(doc! { field: { "$in": values } })
250 .await
251 }
252
253 pub async fn by_field_with_customer_filter(
255 &self,
256 cid: &ObjectId,
257 field: &str,
258 value: &str,
259 ) -> qm_mongodb::error::Result<Option<T>> {
260 self.as_ref()
261 .find_one(doc! {
262 "owner.entityId.cid": &cid,
263 field: value
264 })
265 .await
266 }
267
268 pub async fn list(
270 &self,
271 query: Option<Document>,
272 filter: Option<ListFilter>,
273 ) -> qm_mongodb::error::Result<ListResult<T>> {
274 let query = query.unwrap_or_default();
275 let limit = filter
276 .as_ref()
277 .and_then(|filter| filter.limit.as_ref().copied())
278 .unwrap_or(1000) as i64;
279 let page = filter
280 .as_ref()
281 .and_then(|filter| filter.page.as_ref().copied())
282 .unwrap_or(0);
283 let offset = page as u64 * limit as u64;
284 let total = self.as_ref().count_documents(query.clone()).await?;
285 let options = FindOptions::builder().limit(limit).skip(offset).build();
286
287 let items = self
288 .as_ref()
289 .find(query)
290 .with_options(options)
291 .await?
292 .try_collect::<Vec<T>>()
293 .await?;
294 Ok(ListResult {
295 items,
296 limit: Some(limit),
297 total: Some(total as i64),
298 page: Some(page as i64),
299 })
300 }
301}
302
303impl<T> Collection<T>
304where
305 T: Serialize + Send + Sync + Unpin + AsMut<Option<ID>>,
306{
307 pub async fn save(&self, mut value: T) -> qm_mongodb::error::Result<T> {
309 let id: qm_mongodb::bson::Bson = self.as_ref().insert_one(&value).await?.inserted_id;
310 if let qm_mongodb::bson::Bson::ObjectId(cid) = id {
311 *value.as_mut() = Some(cid.into());
312 }
313 Ok(value)
314 }
315}
316
317pub trait Create<T, C: UserId> {
322 fn create(self, ctx: &C) -> EntityResult<T>;
324}
325
326#[doc(hidden)]
327pub mod __private {
328 pub use crate::error::EntityError;
329 #[doc(hidden)]
330 pub use core::result::Result::Err;
331}
332
333#[macro_export]
335macro_rules! err {
336 ($($arg:tt)*) => {
337 $crate::__private::Err($crate::__private::EntityError::$($arg)*)
338 };
339}
340
341#[macro_export]
343macro_rules! exerr {
344 ($($arg:tt)*) => {
345 $crate::__private::Err($crate::__private::EntityError::$($arg)*.extend())
346 };
347}