use std::{pin::Pin, sync::Arc}; use async_graphql::dynamic::{ Field, FieldFuture, FieldValue, InputObject, InputValue, Object, ObjectAccessor, ResolverContext, TypeRef, ValueAccessor, }; use sea_orm::{ ActiveModelTrait, Condition, EntityTrait, IntoActiveModel, QueryFilter, TransactionTrait, }; use seaography::{ BuilderContext, GuardAction, SeaographyError, get_filter_conditions, prepare_active_model, }; use crate::{ app::AppContextTrait, errors::RecorderResult, graphql::infra::name::{ get_entity_and_column_name_from_column_str, get_entity_basic_type_name, get_entity_create_batch_mutation_data_field_name, get_entity_create_batch_mutation_field_name, get_entity_create_one_mutation_data_field_name, get_entity_create_one_mutation_field_name, get_entity_delete_mutation_field_name, get_entity_delete_mutation_filter_field_name, get_entity_filter_input_type_name, get_entity_insert_data_input_type_name, get_entity_name, get_entity_renormalized_filter_field_name, get_entity_update_data_input_type_name, get_entity_update_mutation_data_field_name, get_entity_update_mutation_field_name, get_entity_update_mutation_filter_field_name, }, }; pub type FilterMutationFn = Arc< dyn for<'a> Fn( &ResolverContext<'a>, Arc, Option>, ) -> Pin< Box>>> + Send + 'a>, > + Send + Sync, >; pub type CreateOneMutationFn = Arc< dyn for<'a> Fn( &ResolverContext<'a>, Arc, ObjectAccessor<'_>, ) -> Pin> + Send + 'a>> + Send + Sync, >; pub type CreateBatchMutationFn = Arc< dyn for<'a> Fn( &ResolverContext<'a>, Arc, Vec>, ) -> Pin>> + Send + 'a>> + Send + Sync, >; pub type UpdateMutationFn = Arc< dyn for<'a> Fn( &ResolverContext<'a>, Arc, Condition, ObjectAccessor<'_>, ) -> Pin>> + Send + 'a>> + Send + Sync, >; pub type DeleteMutationFn = Arc< dyn for<'a> Fn( &ResolverContext<'a>, Arc, Condition, ) -> Pin> + Send + 'a>> + Send + Sync, >; pub fn generate_entity_default_insert_input_object( builder_context: &'static BuilderContext, ) -> InputObject where T: EntityTrait, ::Model: Sync, { let entity_input_builder = seaography::EntityInputBuilder { context: builder_context, }; entity_input_builder.insert_input_object::() } pub fn generate_entity_default_update_input_object( builder_context: &'static BuilderContext, ) -> InputObject where T: EntityTrait, ::Model: Sync, { let entity_input_builder = seaography::EntityInputBuilder { context: builder_context, }; entity_input_builder.update_input_object::() } pub fn generate_entity_default_basic_entity_object( builder_context: &'static BuilderContext, ) -> Object where T: EntityTrait, ::Model: Sync, { let entity_object_builder = seaography::EntityObjectBuilder { context: builder_context, }; entity_object_builder.basic_to_object::() } pub fn generate_entity_filtered_mutation_field( builder_context: &'static BuilderContext, field_name: N, type_ref: R, mutation_fn: FilterMutationFn, ) -> Field where E: EntityTrait, ::Model: Sync, N: Into, R: Into, { let object_name: String = get_entity_name::(builder_context); let guard = builder_context.guards.entity_guards.get(&object_name); Field::new(field_name, type_ref, 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::, async_graphql::Error>(async_graphql::Error::new( reason.unwrap_or("Entity guard triggered.".into()), )); } let app_ctx = ctx.data::>()?; let filters = ctx.args.get(get_entity_renormalized_filter_field_name()); let result = mutation_fn(&ctx, app_ctx.clone(), filters) .await .map_err(async_graphql::Error::new_with_source)?; Ok(result) }) }) .argument(InputValue::new( get_entity_renormalized_filter_field_name(), TypeRef::named(get_entity_filter_input_type_name::(builder_context)), )) } pub fn generate_entity_create_one_mutation_field( builder_context: &'static BuilderContext, input_data_type_ref: Option, mutation_fn: CreateOneMutationFn, ) -> Field where E: EntityTrait, ::Model: Sync, ID: Into, { let guard = builder_context .guards .entity_guards .get(&get_entity_name::(builder_context)); let field_guards = &builder_context.guards.field_guards; Field::new( get_entity_create_one_mutation_field_name::(builder_context), TypeRef::named_nn(get_entity_basic_type_name::(builder_context)), 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::, async_graphql::Error>(async_graphql::Error::new( reason.unwrap_or("Entity guard triggered.".into()), )); } let app_ctx = ctx.data::>()?; let value_accessor = ctx .args .get(get_entity_create_one_mutation_data_field_name( builder_context, )) .unwrap(); let input_object = value_accessor.object()?; for (column, _) in input_object.iter() { let field_guard = field_guards.get( &get_entity_and_column_name_from_column_str::(builder_context, column), ); let field_guard_flag = if let Some(field_guard) = field_guard { (*field_guard)(&ctx) } else { GuardAction::Allow }; if let GuardAction::Block(reason) = field_guard_flag { return match reason { Some(reason) => Err::, async_graphql::Error>( async_graphql::Error::new(reason), ), None => Err::, async_graphql::Error>( async_graphql::Error::new("Field guard triggered."), ), }; } } let result = mutation_fn(&ctx, app_ctx.clone(), input_object) .await .map_err(async_graphql::Error::new_with_source)?; Ok(Some(FieldValue::owned_any(result))) }) }, ) .argument(InputValue::new( get_entity_create_one_mutation_data_field_name(builder_context), input_data_type_ref.map(|t| t.into()).unwrap_or_else(|| { TypeRef::named_nn(get_entity_insert_data_input_type_name::(builder_context)) }), )) } pub fn generate_entity_default_create_one_mutation_fn( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> CreateOneMutationFn where T: EntityTrait, ::Model: Sync + IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { Arc::new(move |resolve_context, app_ctx, input_object| { let entity_input_builder = seaography::EntityInputBuilder { context: builder_context, }; let entity_object_builder = seaography::EntityObjectBuilder { context: builder_context, }; let active_model = prepare_active_model::( &entity_input_builder, &entity_object_builder, &input_object, resolve_context, ) .map_err(SeaographyError::AsyncGraphQLError); Box::pin(async move { if active_model_hooks { let transaction = app_ctx.db().begin().await?; let active_model = active_model?; let active_model = active_model.before_save(&transaction, true).await?; let result: T::Model = active_model.insert(&transaction).await?; let result = A::after_save(result, &transaction, true).await?; transaction.commit().await?; Ok(result) } else { let db = app_ctx.db(); let active_model = active_model?; let result: T::Model = active_model.insert(db).await?; Ok(result) } }) }) } pub fn generate_entity_default_create_one_mutation_field( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> Field where E: EntityTrait, ::Model: Sync, ::Model: IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { generate_entity_create_one_mutation_field::( builder_context, None, generate_entity_default_create_one_mutation_fn::(builder_context, active_model_hooks), ) } pub fn generate_entity_create_batch_mutation_field( builder_context: &'static BuilderContext, input_data_type_ref: Option, mutation_fn: CreateBatchMutationFn, ) -> Field where E: EntityTrait, ::Model: Sync, ID: Into, { let object_name: String = get_entity_name::(builder_context); let guard = builder_context.guards.entity_guards.get(&object_name); let field_guards = &builder_context.guards.field_guards; Field::new( get_entity_create_batch_mutation_field_name::(builder_context), TypeRef::named_nn_list_nn(get_entity_basic_type_name::(builder_context)), 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 match reason { Some(reason) => Err::, async_graphql::Error>( async_graphql::Error::new(reason), ), None => Err::, async_graphql::Error>(async_graphql::Error::new( "Entity guard triggered.", )), }; } let mut input_objects: Vec> = vec![]; let list = ctx .args .get(get_entity_create_batch_mutation_data_field_name( builder_context, )) .unwrap() .list()?; for input in list.iter() { let input_object = input.object()?; for (column, _) in input_object.iter() { let field_guard = field_guards.get(&get_entity_and_column_name_from_column_str::( builder_context, column, )); let field_guard_flag = if let Some(field_guard) = field_guard { (*field_guard)(&ctx) } else { GuardAction::Allow }; if let GuardAction::Block(reason) = field_guard_flag { return match reason { Some(reason) => Err::, async_graphql::Error>( async_graphql::Error::new(reason), ), None => Err::, async_graphql::Error>( async_graphql::Error::new("Field guard triggered."), ), }; } } input_objects.push(input_object); } let app_ctx = ctx.data::>()?; let results = mutation_fn(&ctx, app_ctx.clone(), input_objects) .await .map_err(async_graphql::Error::new_with_source)?; Ok(Some(FieldValue::list( results.into_iter().map(FieldValue::owned_any), ))) }) }, ) .argument(InputValue::new( get_entity_create_batch_mutation_data_field_name(builder_context), input_data_type_ref.map(|t| t.into()).unwrap_or_else(|| { TypeRef::named_nn_list_nn(get_entity_insert_data_input_type_name::(builder_context)) }), )) } pub fn generate_entity_default_create_batch_mutation_fn( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> CreateBatchMutationFn where E: EntityTrait, ::Model: Sync, ::Model: IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { Arc::new(move |resolve_context, app_ctx, input_objects| { let entity_input_builder = seaography::EntityInputBuilder { context: builder_context, }; let entity_object_builder = seaography::EntityObjectBuilder { context: builder_context, }; let active_models = input_objects .into_iter() .map(|input_object| { prepare_active_model::( &entity_input_builder, &entity_object_builder, &input_object, resolve_context, ) }) .collect::, _>>() .map_err(SeaographyError::AsyncGraphQLError); Box::pin(async move { if active_model_hooks { let transaction = app_ctx.db().begin().await?; let mut before_save_models = vec![]; for active_model in active_models? { let before_save_model = active_model.before_save(&transaction, false).await?; before_save_models.push(before_save_model); } let models: Vec = E::insert_many(before_save_models) .exec_with_returning_many(&transaction) .await?; let mut result = vec![]; for model in models { let after_save_model = A::after_save(model, &transaction, false).await?; result.push(after_save_model); } transaction.commit().await?; Ok(result) } else { let db = app_ctx.db(); let active_models = active_models?; let results: Vec = E::insert_many(active_models) .exec_with_returning_many(db) .await?; Ok(results) } }) }) } pub fn generate_entity_default_create_batch_mutation_field( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> Field where E: EntityTrait, ::Model: Sync, ::Model: IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { generate_entity_create_batch_mutation_field::( builder_context, None, generate_entity_default_create_batch_mutation_fn::( builder_context, active_model_hooks, ), ) } pub fn generate_entity_update_mutation_field( builder_context: &'static BuilderContext, input_data_type_ref: Option, mutation_fn: UpdateMutationFn, ) -> Field where E: EntityTrait, ::Model: Sync, I: Into, { let guard = builder_context .guards .entity_guards .get(&get_entity_name::(builder_context)); let field_guards = &builder_context.guards.field_guards; Field::new( get_entity_update_mutation_field_name::(builder_context), TypeRef::named_nn_list_nn(get_entity_basic_type_name::(builder_context)), 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 match reason { Some(reason) => Err::, async_graphql::Error>( async_graphql::Error::new(reason), ), None => Err::, async_graphql::Error>(async_graphql::Error::new( "Entity guard triggered.", )), }; } let app_ctx = ctx.data::>()?; let filters = ctx.args.get(get_entity_update_mutation_filter_field_name( builder_context, )); let filter_condition = get_filter_conditions::(&ctx, builder_context, filters); let value_accessor = ctx .args .get(get_entity_update_mutation_data_field_name(builder_context)) .unwrap(); let input_object = value_accessor.object()?; for (column, _) in input_object.iter() { let field_guard = field_guards.get( &get_entity_and_column_name_from_column_str::(builder_context, column), ); let field_guard_flag = if let Some(field_guard) = field_guard { (*field_guard)(&ctx) } else { GuardAction::Allow }; if let GuardAction::Block(reason) = field_guard_flag { return match reason { Some(reason) => Err::, async_graphql::Error>( async_graphql::Error::new(reason), ), None => Err::, async_graphql::Error>( async_graphql::Error::new("Field guard triggered."), ), }; } } let result = mutation_fn(&ctx, app_ctx.clone(), filter_condition, input_object) .await .map_err(async_graphql::Error::new_with_source)?; Ok(Some(FieldValue::list( result.into_iter().map(FieldValue::owned_any), ))) }) }, ) .argument(InputValue::new( get_entity_update_mutation_data_field_name(builder_context), input_data_type_ref.map(|t| t.into()).unwrap_or_else(|| { TypeRef::named_nn(get_entity_update_data_input_type_name::(builder_context)) }), )) .argument(InputValue::new( get_entity_update_mutation_filter_field_name(builder_context), TypeRef::named(get_entity_filter_input_type_name::(builder_context)), )) } pub fn generate_entity_default_update_mutation_fn( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> UpdateMutationFn where T: EntityTrait, ::Model: Sync + IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { Arc::new( move |resolve_context, app_ctx, filter_condition, input_object| { let entity_input_builder = seaography::EntityInputBuilder { context: builder_context, }; let entity_object_builder = seaography::EntityObjectBuilder { context: builder_context, }; let active_model = prepare_active_model::( &entity_input_builder, &entity_object_builder, &input_object, resolve_context, ) .map_err(SeaographyError::AsyncGraphQLError); Box::pin(async move { if active_model_hooks { let transaction = app_ctx.db().begin().await?; let active_model = active_model?; let active_model = active_model.before_save(&transaction, false).await?; let models = T::update_many() .set(active_model) .filter(filter_condition.clone()) .exec_with_returning(&transaction) .await?; let mut result = vec![]; for model in models { result.push(A::after_save(model, &transaction, false).await?); } transaction.commit().await?; Ok(result) } else { let db = app_ctx.db(); let active_model = active_model?; let result = T::update_many() .set(active_model) .filter(filter_condition.clone()) .exec_with_returning(db) .await?; Ok(result) } }) }, ) } pub fn generate_entity_default_update_mutation_field( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> Field where E: EntityTrait, ::Model: Sync, ::Model: IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { generate_entity_update_mutation_field::( builder_context, None, generate_entity_default_update_mutation_fn::(builder_context, active_model_hooks), ) } pub fn generate_entity_delete_mutation_field( builder_context: &'static BuilderContext, mutation_fn: DeleteMutationFn, ) -> Field where E: EntityTrait, ::Model: Sync, { let object_name: String = get_entity_name::(builder_context); let guard = builder_context.guards.entity_guards.get(&object_name); Field::new( get_entity_delete_mutation_field_name::(builder_context), 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::, async_graphql::Error>(async_graphql::Error::new( reason.unwrap_or("Entity guard triggered.".into()), )); } let filters = ctx.args.get(get_entity_delete_mutation_filter_field_name( builder_context, )); let filter_condition = get_filter_conditions::(&ctx, builder_context, filters); let app_ctx = ctx.data::>()?; let res = mutation_fn(&ctx, app_ctx.clone(), filter_condition) .await .map_err(async_graphql::Error::new_with_source)?; Ok(Some(async_graphql::Value::from(res))) }) }, ) .argument(InputValue::new( get_entity_delete_mutation_filter_field_name(builder_context), TypeRef::named(get_entity_filter_input_type_name::(builder_context)), )) } pub fn generate_entity_default_delete_mutation_fn( _builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> DeleteMutationFn where E: EntityTrait, ::Model: Sync, ::Model: IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { Arc::new(move |_resolve_context, app_ctx, filter_condition| { Box::pin(async move { if active_model_hooks { let transaction = app_ctx.db().begin().await?; let models: Vec = E::find() .filter(filter_condition.clone()) .all(&transaction) .await?; let mut active_models: Vec = vec![]; for model in models { let active_model = model.into_active_model(); active_models.push(active_model.before_delete(&transaction).await?); } let result = E::delete_many() .filter(filter_condition) .exec(&transaction) .await?; for active_model in active_models { active_model.after_delete(&transaction).await?; } transaction.commit().await?; Ok(result.rows_affected) } else { let db = app_ctx.db(); let result = E::delete_many().filter(filter_condition).exec(db).await?; Ok(result.rows_affected) } }) }) } pub fn generate_entity_default_delete_mutation_field( builder_context: &'static BuilderContext, active_model_hooks: bool, ) -> Field where E: EntityTrait, ::Model: Sync, ::Model: IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { generate_entity_delete_mutation_field::( builder_context, generate_entity_default_delete_mutation_fn::(builder_context, active_model_hooks), ) }