feat: try views and seaography

This commit is contained in:
2025-06-15 05:02:23 +08:00
parent a2254bbe80
commit 7eb4e41708
12 changed files with 257 additions and 15 deletions

View File

@@ -0,0 +1,79 @@
use std::{pin::Pin, sync::Arc};
use async_graphql::dynamic::{
Field, FieldFuture, InputValue, ResolverContext, TypeRef, ValueAccessor,
};
use sea_orm::EntityTrait;
use seaography::{
BuilderContext, EntityDeleteMutationBuilder, EntityObjectBuilder, FilterInputBuilder,
GuardAction,
};
use crate::{app::AppContextTrait, errors::RecorderResult};
pub type DeleteMutationFn = Arc<
dyn Fn(
&ResolverContext<'_>,
Arc<dyn AppContextTrait>,
Option<ValueAccessor<'_>>,
) -> Pin<Box<dyn Future<Output = RecorderResult<Option<i32>>> + Send>>
+ Send
+ Sync,
>;
pub fn generate_custom_entity_delete_mutation_field<T>(
builder_context: &'static BuilderContext,
mutation_fn: DeleteMutationFn,
) -> Field
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_filter_input_builder = FilterInputBuilder {
context: builder_context,
};
let entity_object_builder = EntityObjectBuilder {
context: builder_context,
};
let entity_delete_mutation_builder = EntityDeleteMutationBuilder {
context: builder_context,
};
let object_name: String = entity_object_builder.type_name::<T>();
let context = builder_context;
let guard = builder_context.guards.entity_guards.get(&object_name);
Field::new(
entity_delete_mutation_builder.type_name::<T>(),
TypeRef::named_nn(TypeRef::INT),
move |ctx| {
let mutation_fn = mutation_fn.clone();
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
} else {
GuardAction::Allow
};
if let GuardAction::Block(reason) = guard_flag {
return Err::<Option<_>, async_graphql::Error>(async_graphql::Error::new(
reason.unwrap_or("Entity guard triggered.".into()),
));
}
let app_ctx = ctx.data::<Arc<dyn AppContextTrait>>()?;
let filters = ctx.args.get(&context.entity_delete_mutation.filter_field);
let result = mutation_fn(&ctx, app_ctx.clone(), filters).await?;
Ok(result.map(async_graphql::Value::from))
})
},
)
.argument(InputValue::new(
&context.entity_delete_mutation.filter_field,
TypeRef::named(entity_filter_input_builder.type_name(&object_name)),
))
}

View File

@@ -3,6 +3,7 @@ use async_graphql::{
dynamic::{ResolverContext, Scalar, SchemaError},
to_value,
};
use convert_case::Case;
use itertools::Itertools;
use rust_decimal::{Decimal, prelude::FromPrimitive};
use sea_orm::{
@@ -12,9 +13,13 @@ use sea_orm::{
use seaography::{
Builder as SeaographyBuilder, BuilderContext, FilterType, FnFilterCondition, SeaographyError,
};
use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value as JsonValue;
use crate::{errors::RecorderResult, graphql::infra::util::get_entity_column_key};
use crate::{
errors::RecorderResult, graphql::infra::util::get_entity_column_key,
infra::json::convert_json_keys,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
pub enum JsonbFilterOperation {
@@ -948,6 +953,64 @@ where
);
}
pub fn validate_jsonb_input_for_entity<T, S>(context: &mut BuilderContext, column: &T::Column)
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
S: DeserializeOwned + Serialize,
{
let entity_column_key = get_entity_column_key::<T>(context, column);
context.types.input_conversions.insert(
entity_column_key.clone(),
Box::new(move |_resolve_context, accessor| {
let deserialized = accessor.deserialize::<S>().map_err(|err| {
SeaographyError::TypeConversionError(
err.message,
format!("Json - {entity_column_key}"),
)
})?;
let json_value = serde_json::to_value(deserialized).map_err(|err| {
SeaographyError::TypeConversionError(
err.to_string(),
format!("Json - {entity_column_key}"),
)
})?;
Ok(sea_orm::Value::Json(Some(Box::new(json_value))))
}),
);
}
pub fn convert_jsonb_output_case_for_entity<T>(context: &mut BuilderContext, column: &T::Column)
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_column_key = get_entity_column_key::<T>(context, column);
context.types.output_conversions.insert(
entity_column_key.clone(),
Box::new(move |value| {
if let sea_orm::Value::Json(Some(json)) = value {
let result = async_graphql::Value::from_json(convert_json_keys(
json.as_ref().clone(),
Case::Camel,
))
.map_err(|err| {
SeaographyError::TypeConversionError(
err.to_string(),
format!("Json - {entity_column_key}"),
)
})?;
Ok(result)
} else {
Err(SeaographyError::TypeConversionError(
"value should be json".to_string(),
format!("Json - {entity_column_key}"),
))
}
}),
);
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;

View File

@@ -1,2 +1,3 @@
pub mod custom;
pub mod json;
pub mod util;