diff --git a/.vscode/settings.json b/.vscode/settings.json index c37d92d..a638584 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,13 +40,9 @@ } ], "rust-analyzer.cargo.features": "all", - "rust-analyzer.testExplorer": true + "rust-analyzer.testExplorer": true, // https://github.com/rust-lang/rust/issues/141540 - // "rust-analyzer.cargo.targetDir": "target/rust-analyzer", - // "rust-analyzer.check.extraEnv": { - // "CARGO_TARGET_DIR": "target/rust-analyzer" - // }, - // "rust-analyzer.cargo.extraEnv": { - // "CARGO_TARGET_DIR": "target/analyzer" - // } + "rust-analyzer.runnables.extraEnv": { + "CARGO_INCREMENTAL": "0", + } } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f7d6d17..29aa39b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7791,16 +7791,15 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "seaography" version = "1.1.4" -source = "git+https://github.com/dumtruck/seaography.git?rev=a787c3a#a787c3ab83cf1f8275894e1bc1ca3c766b54674b" dependencies = [ "async-graphql", "fnv", - "heck 0.4.1", - "itertools 0.12.1", + "heck 0.5.0", + "itertools 0.14.0", "lazy_static", "sea-orm", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ab48481..bb74589 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ resolver = "2" [profile.dev] debug = 0 # https://github.com/rust-lang/rust/issues/141540 -incremental = false +incremental = true # Then only change rust-analyzer incremental # [simd not supported by cranelift](https://github.com/rust-lang/rustc_codegen_cranelift/issues/171) # codegen-backend = "cranelift" @@ -87,4 +87,6 @@ nanoid = "0.4.0" webp = "0.3.0" [patch.crates-io] -seaography = { git = "https://github.com/dumtruck/seaography.git", rev = "a787c3a" } +# seaography = { git = "https://github.com/dumtruck/seaography.git", rev = "395d50f" } + +seaography = { path = "../seaography" } diff --git a/apps/recorder/bindings/SubscriberTask.ts b/apps/recorder/bindings/SubscriberTask.ts new file mode 100644 index 0000000..d12ac40 --- /dev/null +++ b/apps/recorder/bindings/SubscriberTask.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SyncOneSubscriptionFeedsFullTask } from "./SyncOneSubscriptionFeedsFullTask"; +import type { SyncOneSubscriptionFeedsIncrementalTask } from "./SyncOneSubscriptionFeedsIncrementalTask"; +import type { SyncOneSubscriptionSourcesTask } from "./SyncOneSubscriptionSourcesTask"; + +export type SubscriberTask = { "taskType": "sync_one_subscription_feeds_incremental" } & SyncOneSubscriptionFeedsIncrementalTask | { "taskType": "sync_one_subscription_feeds_full" } & SyncOneSubscriptionFeedsFullTask | { "taskType": "sync_one_subscription_sources" } & SyncOneSubscriptionSourcesTask; diff --git a/apps/recorder/bindings/SyncOneSubscriptionFeedsFullTask.ts b/apps/recorder/bindings/SyncOneSubscriptionFeedsFullTask.ts new file mode 100644 index 0000000..644ca01 --- /dev/null +++ b/apps/recorder/bindings/SyncOneSubscriptionFeedsFullTask.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SyncOneSubscriptionFeedsFullTask = { subscriptionId: number, subscriberId: number, cronId: number | null, }; diff --git a/apps/recorder/bindings/SyncOneSubscriptionFeedsIncrementalTask.ts b/apps/recorder/bindings/SyncOneSubscriptionFeedsIncrementalTask.ts new file mode 100644 index 0000000..1f3e9a4 --- /dev/null +++ b/apps/recorder/bindings/SyncOneSubscriptionFeedsIncrementalTask.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SyncOneSubscriptionFeedsIncrementalTask = { subscriptionId: number, subscriberId: number, cronId: number | null, }; diff --git a/apps/recorder/bindings/SyncOneSubscriptionSourcesTask.ts b/apps/recorder/bindings/SyncOneSubscriptionSourcesTask.ts new file mode 100644 index 0000000..01a5d5e --- /dev/null +++ b/apps/recorder/bindings/SyncOneSubscriptionSourcesTask.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SyncOneSubscriptionSourcesTask = { subscriptionId: number, subscriberId: number, cronId: number | null, }; diff --git a/apps/recorder/package.json b/apps/recorder/package.json new file mode 100644 index 0000000..354e410 --- /dev/null +++ b/apps/recorder/package.json @@ -0,0 +1,6 @@ +{ + "name": "recorder", + "version": "0.0.1", + "private": true, + "type": "module" +} diff --git a/apps/recorder/src/graphql/domains/credential_3rd.rs b/apps/recorder/src/graphql/domains/credential_3rd.rs index 0c2aff5..5844252 100644 --- a/apps/recorder/src/graphql/domains/credential_3rd.rs +++ b/apps/recorder/src/graphql/domains/credential_3rd.rs @@ -101,14 +101,14 @@ pub fn register_credential3rd_to_schema_builder( .schema .register(Credential3rdCheckAvailableInfo::generate_output_object()); - let builder_context = builder.context; + let builder_context = &builder.context; { let check_available_mutation_name = get_entity_custom_mutation_field_name::< credential_3rd::Entity, - >(builder_context, "CheckAvailable"); + >(&builder_context, "CheckAvailable"); let check_available_mutation = generate_entity_filtered_mutation_field::( - builder_context, + builder_context.clone(), check_available_mutation_name, TypeRef::named_nn(Credential3rdCheckAvailableInfo::object_type_name()), Arc::new(|_resolver_ctx, app_ctx, filters| { diff --git a/apps/recorder/src/graphql/domains/cron.rs b/apps/recorder/src/graphql/domains/cron.rs index e35cc91..5de6e63 100644 --- a/apps/recorder/src/graphql/domains/cron.rs +++ b/apps/recorder/src/graphql/domains/cron.rs @@ -4,17 +4,13 @@ use seaography::{Builder as SeaographyBuilder, BuilderContext}; use crate::{ graphql::{ - domains::subscribers::restrict_subscriber_for_entity, - infra::{ - custom::register_entity_default_writable, - json::{ - convert_jsonb_output_for_entity, restrict_jsonb_filter_input_for_entity, - try_convert_jsonb_input_for_entity, - }, - name::get_entity_and_column_name, + domains::{ + subscriber_tasks::restrict_subscriber_tasks_for_entity, + subscribers::restrict_subscriber_for_entity, }, + infra::{custom::register_entity_default_writable, name::get_entity_and_column_name}, }, - models::{cron, subscriber_tasks}, + models::cron, }; fn skip_columns_for_entity_input(context: &mut BuilderContext) { @@ -22,7 +18,6 @@ fn skip_columns_for_entity_input(context: &mut BuilderContext) { if matches!( column, cron::Column::SubscriberTask - | cron::Column::Id | cron::Column::CronExpr | cron::Column::Enabled | cron::Column::TimeoutMs @@ -49,16 +44,9 @@ fn skip_columns_for_entity_input(context: &mut BuilderContext) { pub fn register_cron_to_schema_context(context: &mut BuilderContext) { restrict_subscriber_for_entity::(context, &cron::Column::SubscriberId); - restrict_jsonb_filter_input_for_entity::(context, &cron::Column::SubscriberTask); - convert_jsonb_output_for_entity::( + restrict_subscriber_tasks_for_entity::( context, &cron::Column::SubscriberTask, - Some(Case::Camel), - ); - try_convert_jsonb_input_for_entity::>( - context, - &cron::Column::SubscriberTask, - subscriber_tasks::subscriber_task_schema(), Some(Case::Snake), ); skip_columns_for_entity_input(context); diff --git a/apps/recorder/src/graphql/domains/feeds.rs b/apps/recorder/src/graphql/domains/feeds.rs index c044c6a..a106a36 100644 --- a/apps/recorder/src/graphql/domains/feeds.rs +++ b/apps/recorder/src/graphql/domains/feeds.rs @@ -29,7 +29,7 @@ pub fn register_feeds_to_schema_context(context: &mut BuilderContext) { context.types.input_none_conversions.insert( get_entity_and_column_name::(context, &feeds::Column::Token), - Box::new( + Arc::new( move |context: &ResolverContext| -> SeaResult> { let field_name = context.field().name(); if field_name == entity_create_one_mutation_field_name.as_str() diff --git a/apps/recorder/src/graphql/domains/subscriber_tasks.rs b/apps/recorder/src/graphql/domains/subscriber_tasks.rs index 2fb343a..d11d3ca 100644 --- a/apps/recorder/src/graphql/domains/subscriber_tasks.rs +++ b/apps/recorder/src/graphql/domains/subscriber_tasks.rs @@ -3,15 +3,15 @@ use std::{ops::Deref, sync::Arc}; use async_graphql::dynamic::{FieldValue, TypeRef}; use convert_case::Case; use sea_orm::{ - ColumnTrait, ConnectionTrait, EntityTrait, Iterable, QueryFilter, QuerySelect, QueryTrait, - prelude::Expr, sea_query::Query, + ActiveModelBehavior, ColumnTrait, ConnectionTrait, EntityTrait, Iterable, QueryFilter, + QuerySelect, QueryTrait, prelude::Expr, sea_query::Query, }; use seaography::{ - Builder as SeaographyBuilder, BuilderContext, EntityInputBuilder, EntityObjectBuilder, - SeaographyError, prepare_active_model, + Builder as SeaographyBuilder, BuilderContext, SeaographyError, prepare_active_model, }; use crate::{ + app::AppContextTrait, auth::AuthUserInfo, errors::RecorderError, graphql::{ @@ -64,13 +64,17 @@ pub fn restrict_subscriber_tasks_for_entity( let entity_column_name = get_entity_and_column_name::(context, column); context.types.input_conversions.insert( entity_column_name.clone(), - Box::new(move |resolve_context, accessor| { - let mut json_value = accessor.as_value().clone().into_json().map_err(|err| { - SeaographyError::TypeConversionError( - err.to_string(), - format!("Json - {entity_column_name}"), - ) - })?; + Arc::new(move |resolve_context, value_accessor| { + let mut json_value = value_accessor + .as_value() + .clone() + .into_json() + .map_err(|err| { + SeaographyError::TypeConversionError( + err.to_string(), + format!("Json - {entity_column_name}"), + ) + })?; if let Some(case) = case { json_value = convert_json_keys(json_value, case); @@ -107,6 +111,11 @@ pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext) context, &subscriber_tasks::Column::SubscriberId, ); + restrict_subscriber_tasks_for_entity::( + context, + &subscriber_tasks::Column::Job, + Some(Case::Snake), + ); skip_columns_for_entity_input(context); } @@ -118,18 +127,18 @@ pub fn register_subscriber_tasks_to_schema_builder( builder.register_enumeration::(); builder = register_entity_default_readonly!(builder, subscriber_tasks); + let builder_context = builder.context.clone(); - let builder_context = builder.context; { builder .outputs .push(generate_entity_default_basic_entity_object::< subscriber_tasks::Entity, - >(builder_context)); + >(builder_context.clone())); } { let delete_mutation = generate_entity_delete_mutation_field::( - builder_context, + builder_context.clone(), Arc::new(|_resolver_ctx, app_ctx, filters| { Box::pin(async move { let db = app_ctx.db(); @@ -160,13 +169,13 @@ pub fn register_subscriber_tasks_to_schema_builder( { let entity_retry_one_mutation_name = get_entity_custom_mutation_field_name::< subscriber_tasks::Entity, - >(builder_context, "RetryOne"); + >(&builder_context, "RetryOne"); let retry_one_mutation = generate_entity_filtered_mutation_field::( - builder_context, + builder_context.clone(), entity_retry_one_mutation_name, TypeRef::named_nn(get_entity_basic_type_name::( - builder_context, + &builder_context, )), Arc::new(|_resolver_ctx, app_ctx, filters| { Box::pin(async move { @@ -205,32 +214,23 @@ pub fn register_subscriber_tasks_to_schema_builder( .inputs .push(generate_entity_default_insert_input_object::< subscriber_tasks::Entity, - >(builder_context)); + >(&builder.context)); let create_one_mutation = - generate_entity_create_one_mutation_field::( - builder_context, - None, - Arc::new(|_resolver_ctx, app_ctx, input_object| { - let entity_input_builder = EntityInputBuilder { - context: builder_context, - }; - let entity_object_builder = EntityObjectBuilder { - context: builder_context, - }; - + generate_entity_create_one_mutation_field::( + builder.context.clone(), + Arc::new(move |resolver_ctx, app_ctx, input_object| { let active_model: Result = - prepare_active_model( - &entity_input_builder, - &entity_object_builder, - &input_object, - _resolver_ctx, - ); + prepare_active_model(&builder_context.clone(), &input_object, resolver_ctx); Box::pin(async move { let task_service = app_ctx.task(); let active_model = active_model?; + let db = app_ctx.db(); + + let active_model = active_model.before_save(db, true).await?; + let task = active_model.job.unwrap(); let subscriber_id = active_model.subscriber_id.unwrap(); diff --git a/apps/recorder/src/graphql/domains/subscribers.rs b/apps/recorder/src/graphql/domains/subscribers.rs index a8b15e4..ce99711 100644 --- a/apps/recorder/src/graphql/domains/subscribers.rs +++ b/apps/recorder/src/graphql/domains/subscribers.rs @@ -79,7 +79,7 @@ where T: EntityTrait, ::Model: Sync, { - Box::new(move |context: &ResolverContext| -> GuardAction { + Arc::new(move |context: &ResolverContext| -> GuardAction { match context.ctx.data::() { Ok(_) => GuardAction::Allow, Err(err) => GuardAction::Block(Some(err.message)), @@ -106,7 +106,7 @@ where let entity_update_mutation_data_field_name = Arc::new(get_entity_update_mutation_data_field_name(context).to_string()); - Box::new(move |context: &ResolverContext| -> GuardAction { + Arc::new(move |context: &ResolverContext| -> GuardAction { match context.ctx.data::() { Ok(user_info) => { let subscriber_id = user_info.subscriber_auth.subscriber_id; @@ -253,7 +253,7 @@ where Arc::new(get_entity_create_one_mutation_field_name::(context)); let entity_create_batch_mutation_field_name = Arc::new(get_entity_create_batch_mutation_field_name::(context)); - Box::new( + Arc::new( move |context: &ResolverContext| -> SeaResult> { let field_name = context.field().name(); if field_name == entity_create_one_mutation_field_name.as_str() @@ -318,13 +318,11 @@ pub fn register_subscribers_to_schema_context(context: &mut BuilderContext) { pub fn register_subscribers_to_schema_builder(mut builder: SeaographyBuilder) -> SeaographyBuilder { { - let filter_types_map_helper = FilterTypesMapHelper { - context: builder.context, - }; - builder.schema = builder .schema - .register(filter_types_map_helper.generate_filter_input(&SUBSCRIBER_ID_FILTER_INFO)); + .register(FilterTypesMapHelper::generate_filter_input( + &SUBSCRIBER_ID_FILTER_INFO, + )); } builder = register_entity_default_readonly!(builder, subscribers); diff --git a/apps/recorder/src/graphql/domains/subscriptions.rs b/apps/recorder/src/graphql/domains/subscriptions.rs index 00bfeec..bdb8955 100644 --- a/apps/recorder/src/graphql/domains/subscriptions.rs +++ b/apps/recorder/src/graphql/domains/subscriptions.rs @@ -1,23 +1,11 @@ -use std::sync::Arc; - -use async_graphql::dynamic::{FieldValue, TypeRef}; -use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use seaography::{Builder as SeaographyBuilder, BuilderContext}; use crate::{ - errors::RecorderError, graphql::{ - domains::subscribers::restrict_subscriber_for_entity, - infra::{ - custom::{generate_entity_filtered_mutation_field, register_entity_default_writable}, - name::{get_entity_basic_type_name, get_entity_custom_mutation_field_name}, - }, - }, - models::{subscriber_tasks, subscriptions}, - task::{ - SyncOneSubscriptionFeedsFullTask, SyncOneSubscriptionFeedsIncrementalTask, - SyncOneSubscriptionSourcesTask, + domains::subscribers::restrict_subscriber_for_entity, infra, + infra::custom::register_entity_default_writable, }, + models::subscriptions, }; pub fn register_subscriptions_to_schema_context(context: &mut BuilderContext) { @@ -32,162 +20,5 @@ pub fn register_subscriptions_to_schema_builder( ) -> SeaographyBuilder { builder.register_enumeration::(); builder = register_entity_default_writable!(builder, subscriptions, false); - - let context = builder.context; - - { - let sync_one_feeds_incremental_mutation_name = get_entity_custom_mutation_field_name::< - subscriptions::Entity, - >(context, "SyncOneFeedsIncremental"); - - let sync_one_feeds_incremental_mutation = - generate_entity_filtered_mutation_field::( - builder.context, - sync_one_feeds_incremental_mutation_name, - TypeRef::named_nn(get_entity_basic_type_name::( - context, - )), - Arc::new(|_resolver_ctx, app_ctx, filters| { - Box::pin(async move { - let db = app_ctx.db(); - - let subscription_model = subscriptions::Entity::find() - .filter(filters) - .one(db) - .await? - .ok_or_else(|| { - RecorderError::from_entity_not_found::() - })?; - - let task_service = app_ctx.task(); - - let task_id = task_service - .add_subscriber_task( - SyncOneSubscriptionFeedsIncrementalTask::builder() - .subscriber_id(subscription_model.subscriber_id) - .subscription_id(subscription_model.id) - .build() - .into(), - ) - .await?; - - let task_model = subscriber_tasks::Entity::find() - .filter(subscriber_tasks::Column::Id.eq(task_id.to_string())) - .one(db) - .await? - .ok_or_else(|| { - RecorderError::from_entity_not_found::() - })?; - - Ok(Some(FieldValue::owned_any(task_model))) - }) - }), - ); - - builder.mutations.push(sync_one_feeds_incremental_mutation); - } - { - let sync_one_feeds_full_mutation_name = get_entity_custom_mutation_field_name::< - subscriptions::Entity, - >(builder.context, "SyncOneFeedsFull"); - let sync_one_feeds_full_mutation = - generate_entity_filtered_mutation_field::( - builder.context, - sync_one_feeds_full_mutation_name, - TypeRef::named_nn(get_entity_basic_type_name::( - context, - )), - Arc::new(|_resolver_ctx, app_ctx, filters| { - Box::pin(async move { - let db = app_ctx.db(); - - let subscription_model = subscriptions::Entity::find() - .filter(filters) - .one(db) - .await? - .ok_or_else(|| { - RecorderError::from_entity_not_found::() - })?; - - let task_service = app_ctx.task(); - - let task_id = task_service - .add_subscriber_task( - SyncOneSubscriptionFeedsFullTask::builder() - .subscriber_id(subscription_model.subscriber_id) - .subscription_id(subscription_model.id) - .build() - .into(), - ) - .await?; - - let task_model = subscriber_tasks::Entity::find() - .filter(subscriber_tasks::Column::Id.eq(task_id.to_string())) - .one(db) - .await? - .ok_or_else(|| { - RecorderError::from_entity_not_found::() - })?; - - Ok(Some(FieldValue::owned_any(task_model))) - }) - }), - ); - - builder.mutations.push(sync_one_feeds_full_mutation); - } - - { - let sync_one_sources_mutation_name = get_entity_custom_mutation_field_name::< - subscriptions::Entity, - >(context, "SyncOneSources"); - - let sync_one_sources_mutation = - generate_entity_filtered_mutation_field::( - builder.context, - sync_one_sources_mutation_name, - TypeRef::named_nn(get_entity_basic_type_name::( - context, - )), - Arc::new(|_resolver_ctx, app_ctx, filters| { - Box::pin(async move { - let db = app_ctx.db(); - - let subscription_model = subscriptions::Entity::find() - .filter(filters) - .one(db) - .await? - .ok_or_else(|| { - RecorderError::from_entity_not_found::() - })?; - - let task_service = app_ctx.task(); - - let task_id = task_service - .add_subscriber_task( - SyncOneSubscriptionSourcesTask::builder() - .subscriber_id(subscription_model.subscriber_id) - .subscription_id(subscription_model.id) - .build() - .into(), - ) - .await?; - - let task_model = subscriber_tasks::Entity::find() - .filter(subscriber_tasks::Column::Id.eq(task_id.to_string())) - .one(db) - .await? - .ok_or_else(|| { - RecorderError::from_entity_not_found::() - })?; - - Ok(Some(FieldValue::owned_any(task_model))) - }) - }), - ); - - builder.mutations.push(sync_one_sources_mutation); - } - builder } diff --git a/apps/recorder/src/graphql/infra/crypto.rs b/apps/recorder/src/graphql/infra/crypto.rs index f17cb9c..6c8d624 100644 --- a/apps/recorder/src/graphql/infra/crypto.rs +++ b/apps/recorder/src/graphql/infra/crypto.rs @@ -16,7 +16,7 @@ pub fn register_crypto_column_input_conversion_to_schema_context( { context.types.input_conversions.insert( get_entity_and_column_name::(context, column), - Box::new( + Arc::new( move |_resolve_context: &ResolverContext<'_>, value: &ValueAccessor| -> SeaResult { @@ -38,7 +38,7 @@ pub fn register_crypto_column_output_conversion_to_schema_context( { context.types.output_conversions.insert( get_entity_and_column_name::(context, column), - Box::new( + Arc::new( move |value: &sea_orm::Value| -> SeaResult { if let SeaValue::String(s) = value { if let Some(s) = s { diff --git a/apps/recorder/src/graphql/infra/custom.rs b/apps/recorder/src/graphql/infra/custom.rs index 9e95d6d..30d21b9 100644 --- a/apps/recorder/src/graphql/infra/custom.rs +++ b/apps/recorder/src/graphql/infra/custom.rs @@ -4,27 +4,20 @@ use async_graphql::dynamic::{ Field, FieldFuture, FieldValue, InputObject, InputValue, Object, ObjectAccessor, ResolverContext, TypeRef, }; -use sea_orm::{ - ActiveModelTrait, Condition, EntityTrait, IntoActiveModel, QueryFilter, TransactionTrait, -}; +use sea_orm::{ActiveModelTrait, Condition, EntityTrait, IntoActiveModel}; use seaography::{ - Builder as SeaographyBuilder, BuilderContext, GuardAction, RelationBuilder, - get_filter_conditions, prepare_active_model, + Builder as SeaographyBuilder, BuilderContext, EntityCreateBatchMutationBuilder, + EntityCreateOneMutationBuilder, EntityDeleteMutationBuilder, EntityInputBuilder, + EntityObjectBuilder, EntityUpdateMutationBuilder, GuardAction, RelationBuilder, + get_filter_conditions, }; 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, + get_entity_filter_input_type_name, get_entity_name, + get_entity_renormalized_filter_field_name, }, }; @@ -80,50 +73,47 @@ pub type DeleteMutationFn = Arc< + Sync, >; -pub fn generate_entity_default_insert_input_object( - builder_context: &'static BuilderContext, +pub fn generate_entity_default_insert_input_object(context: &BuilderContext) -> InputObject +where + T: EntityTrait, + ::Model: Sync, +{ + EntityInputBuilder::insert_input_object::(context) +} + +pub fn generate_entity_default_update_input_object(context: &BuilderContext) -> InputObject +where + T: EntityTrait, + ::Model: Sync, +{ + EntityInputBuilder::update_input_object::(context) +} + +pub fn generate_entity_default_basic_entity_object(context: Arc) -> Object +where + T: EntityTrait, + ::Model: Sync, +{ + EntityObjectBuilder::basic_to_object::(context) +} + +pub fn generate_entity_input_object( + context: &'static BuilderContext, + is_insert: bool, ) -> 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::() + if is_insert { + EntityInputBuilder::insert_input_object::(context) + } else { + EntityInputBuilder::update_input_object::(context) + } } pub fn generate_entity_filtered_mutation_field( - builder_context: &'static BuilderContext, + builder_context: Arc, field_name: N, type_ref: R, mutation_fn: FilterMutationFn, @@ -134,19 +124,28 @@ where N: Into, R: Into, { - let object_name: String = get_entity_name::(builder_context); + let object_name: String = get_entity_name::(&builder_context); - let guard = builder_context.guards.entity_guards.get(&object_name); + let guard = builder_context + .guards + .entity_guards + .get(&object_name) + .cloned(); + + let filter_input_value = InputValue::new( + get_entity_renormalized_filter_field_name(), + TypeRef::named(get_entity_filter_input_type_name::(&builder_context)), + ); Field::new(field_name, type_ref, move |ctx| { let mutation_fn = mutation_fn.clone(); + let builder_context = builder_context.clone(); + let guard_flag = if let Some(guard) = guard.as_ref() { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; 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()), @@ -157,7 +156,7 @@ where let filters = ctx.args.get(get_entity_renormalized_filter_field_name()); - let filters = get_filter_conditions::(&ctx, builder_context, filters); + let filters = get_filter_conditions::(&ctx, &builder_context, filters); let result = mutation_fn(&ctx, app_ctx.clone(), filters) .await @@ -166,146 +165,30 @@ where Ok(result) }) }) - .argument(InputValue::new( - get_entity_renormalized_filter_field_name(), - TypeRef::named(get_entity_filter_input_type_name::(builder_context)), - )) + .argument(filter_input_value) } -pub fn generate_entity_create_one_mutation_field( - builder_context: &'static BuilderContext, - input_data_type_ref: Option, +pub fn generate_entity_create_one_mutation_field( + builder_context: Arc, 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)) + EntityCreateOneMutationBuilder::to_field_with_mutation_fn::( + builder_context.clone(), + Arc::new(move |resolver_ctx, input_object| { + let result = resolver_ctx + .data::>() + .map(|app_ctx| mutation_fn(&resolver_ctx, app_ctx.clone(), input_object)); + Box::pin(async move { result?.await.map_err(async_graphql::Error::new_with_source) }) }), - )) -} - -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, - ); - - 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, + builder_context: Arc, active_model_hooks: bool, ) -> Field where @@ -314,174 +197,30 @@ where ::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), - ) + EntityCreateOneMutationBuilder::to_field::(builder_context, active_model_hooks) } pub fn generate_entity_create_batch_mutation_field( - builder_context: &'static BuilderContext, - input_data_type_ref: Option, + builder_context: Arc, 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)) + EntityCreateBatchMutationBuilder::to_field_with_mutation_fn::( + builder_context, + Arc::new(move |resolver_ctx, input_objects| { + let result = resolver_ctx + .data::>() + .map(|app_ctx| mutation_fn(&resolver_ctx, app_ctx.clone(), input_objects)); + Box::pin(async move { result?.await.map_err(async_graphql::Error::new_with_source) }) }), - )) -} - -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::, _>>(); - - 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, + builder_context: Arc, active_model_hooks: bool, ) -> Field where @@ -490,178 +229,37 @@ where ::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, - ), - ) + EntityCreateBatchMutationBuilder::to_field::(builder_context, active_model_hooks) } -pub fn generate_entity_update_mutation_field( - builder_context: &'static BuilderContext, - input_data_type_ref: Option, +pub fn generate_entity_update_mutation_field( + builder_context: Arc, 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)) + EntityUpdateMutationBuilder::to_field_with_mutation_fn::( + builder_context.clone(), + Arc::new(move |resolver_ctx, filters, input_object| { + let result = resolver_ctx + .data::>() + .map(|app_ctx| { + mutation_fn( + &resolver_ctx, + app_ctx.clone(), + get_filter_conditions::(&resolver_ctx, &builder_context, filters), + input_object, + ) + }); + Box::pin(async move { result?.await.map_err(async_graphql::Error::new_with_source) }) }), - )) - .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, - ); - - 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, + builder_context: Arc, active_model_hooks: bool, ) -> Field where @@ -670,114 +268,36 @@ where ::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), - ) + EntityUpdateMutationBuilder::to_field::(builder_context, active_model_hooks) } pub fn generate_entity_delete_mutation_field( - builder_context: &'static BuilderContext, + builder_context: Arc, 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))) - }) - }, + EntityDeleteMutationBuilder::to_field_with_mutation_fn::( + builder_context.clone(), + Arc::new(move |resolver_ctx, filters| { + let result = resolver_ctx + .data::>() + .map(|app_ctx| { + mutation_fn( + &resolver_ctx, + app_ctx.clone(), + get_filter_conditions::(&resolver_ctx, &builder_context, filters), + ) + }); + Box::pin(async move { result?.await.map_err(async_graphql::Error::new_with_source) }) + }), ) - .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, + builder_context: Arc, active_model_hooks: bool, ) -> Field where @@ -786,10 +306,7 @@ where ::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), - ) + EntityDeleteMutationBuilder::to_field::(builder_context, active_model_hooks) } pub fn register_entity_default_mutations( @@ -801,28 +318,35 @@ where ::Model: Sync + IntoActiveModel, A: ActiveModelTrait + sea_orm::ActiveModelBehavior + std::marker::Send + 'static, { + let builder_context = &builder.context; builder .outputs .push(generate_entity_default_basic_entity_object::( - builder.context, + builder_context.clone(), )); builder.inputs.extend([ - generate_entity_default_insert_input_object::(builder.context), - generate_entity_default_update_input_object::(builder.context), + generate_entity_default_insert_input_object::(&builder.context), + generate_entity_default_update_input_object::(&builder.context), ]); builder.mutations.extend([ generate_entity_default_create_one_mutation_field::( - builder.context, + builder_context.clone(), active_model_hooks, ), generate_entity_default_create_batch_mutation_field::( - builder.context, + builder_context.clone(), + active_model_hooks, + ), + generate_entity_default_update_mutation_field::( + builder_context.clone(), + active_model_hooks, + ), + generate_entity_default_delete_mutation_field::( + builder_context.clone(), active_model_hooks, ), - generate_entity_default_update_mutation_field::(builder.context, active_model_hooks), - generate_entity_default_delete_mutation_field::(builder.context, active_model_hooks), ]); builder @@ -840,7 +364,7 @@ where { builder.register_entity::( ::iter() - .map(|rel| RelationBuilder::get_relation(&rel, builder.context)) + .map(|rel| RelationBuilder::get_relation(&rel, builder.context.clone())) .collect(), ); builder = builder.register_entity_dataloader_one_to_one(entity, tokio::spawn); diff --git a/apps/recorder/src/graphql/infra/json.rs b/apps/recorder/src/graphql/infra/json.rs index 1246b9e..c306446 100644 --- a/apps/recorder/src/graphql/infra/json.rs +++ b/apps/recorder/src/graphql/infra/json.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use async_graphql::{ Error as GraphqlError, dynamic::{ResolverContext, Scalar, SchemaError}, @@ -963,7 +965,7 @@ pub fn try_convert_jsonb_input_for_entity( let entity_column_name = get_entity_and_column_name::(context, column); context.types.input_conversions.insert( entity_column_name.clone(), - Box::new(move |_resolve_context, accessor| { + Arc::new(move |_resolve_context, accessor| { let mut json_value = accessor.as_value().clone().into_json().map_err(|err| { SeaographyError::TypeConversionError( err.to_string(), @@ -998,7 +1000,7 @@ pub fn convert_jsonb_output_for_entity( let entity_column_key = get_entity_and_column_name::(context, column); context.types.output_conversions.insert( entity_column_key.clone(), - Box::new(move |value| { + Arc::new(move |value| { if let sea_orm::Value::Json(Some(json)) = value { let mut json_value = json.as_ref().clone(); if let Some(case) = case { diff --git a/apps/recorder/src/graphql/infra/name.rs b/apps/recorder/src/graphql/infra/name.rs index 42fe8cf..1036612 100644 --- a/apps/recorder/src/graphql/infra/name.rs +++ b/apps/recorder/src/graphql/infra/name.rs @@ -78,7 +78,7 @@ where context.filter_input.type_name.as_ref()(&entity_name) } -pub fn get_entity_insert_data_input_type_name(context: &BuilderContext) -> String +pub fn get_entity_insert_input_type_name(context: &BuilderContext) -> String where T: EntityTrait, ::Model: Sync, @@ -87,7 +87,7 @@ where format!("{entity_name}{}", context.entity_input.insert_suffix) } -pub fn get_entity_update_data_input_type_name(context: &BuilderContext) -> String +pub fn get_entity_update_input_type_name(context: &BuilderContext) -> String where T: EntityTrait, ::Model: Sync, diff --git a/apps/recorder/src/graphql/schema.rs b/apps/recorder/src/graphql/schema.rs index c4e18cf..45ba480 100644 --- a/apps/recorder/src/graphql/schema.rs +++ b/apps/recorder/src/graphql/schema.rs @@ -59,27 +59,24 @@ pub fn build_schema( ) -> Result { let database = app_ctx.db().as_ref().clone(); - let context = CONTEXT.get_or_init(|| { + let context = Arc::new({ let mut context = BuilderContext::default(); - + // basic renormalize_filter_field_names_to_schema_context(&mut context); renormalize_data_field_names_to_schema_context(&mut context); - - { - // domains - register_feeds_to_schema_context(&mut context); - register_subscribers_to_schema_context(&mut context); - register_subscriptions_to_schema_context(&mut context); - register_subscriber_tasks_to_schema_context(&mut context); - register_credential3rd_to_schema_context(&mut context, app_ctx.clone()); - register_downloaders_to_schema_context(&mut context); - register_downloads_to_schema_context(&mut context); - register_episodes_to_schema_context(&mut context); - register_subscription_bangumi_to_schema_context(&mut context); - register_subscription_episode_to_schema_context(&mut context); - register_bangumi_to_schema_context(&mut context); - register_cron_to_schema_context(&mut context); - } + // domains + register_feeds_to_schema_context(&mut context); + register_subscribers_to_schema_context(&mut context); + register_subscriptions_to_schema_context(&mut context); + register_subscriber_tasks_to_schema_context(&mut context); + register_credential3rd_to_schema_context(&mut context, app_ctx.clone()); + register_downloaders_to_schema_context(&mut context); + register_downloads_to_schema_context(&mut context); + register_episodes_to_schema_context(&mut context); + register_subscription_bangumi_to_schema_context(&mut context); + register_subscription_episode_to_schema_context(&mut context); + register_bangumi_to_schema_context(&mut context); + register_cron_to_schema_context(&mut context); context }); diff --git a/apps/recorder/src/models/cron/mod.rs b/apps/recorder/src/models/cron/mod.rs index fa8cf0a..13d5a68 100644 --- a/apps/recorder/src/models/cron/mod.rs +++ b/apps/recorder/src/models/cron/mod.rs @@ -128,19 +128,13 @@ impl ActiveModelBehavior for ActiveModel { Model::calculate_next_run(cron_expr).map_err(|e| DbErr::Custom(e.to_string()))?; self.next_run = Set(Some(next_run)); } - if let ActiveValue::Set(Some(subscriber_id)) = self.subscriber_id { - if let ActiveValue::Set(Some(ref subscriber_task)) = self.subscriber_task { - if subscriber_task.get_subscriber_id() != subscriber_id { - return Err(DbErr::Custom( - "Subscriber task subscriber_id does not match cron subscriber_id" - .to_string(), - )); - } - } else { - return Err(DbErr::Custom( - "Cron subscriber_id is set but subscriber_task is not set".to_string(), - )); - } + if let ActiveValue::Set(Some(subscriber_id)) = self.subscriber_id + && let ActiveValue::Set(Some(ref subscriber_task)) = self.subscriber_task + && subscriber_task.get_subscriber_id() != subscriber_id + { + return Err(DbErr::Custom( + "Cron subscriber_id does not match subscriber_task.subscriber_id".to_string(), + )); } Ok(self) diff --git a/apps/recorder/src/models/subscriber_tasks/mod.rs b/apps/recorder/src/models/subscriber_tasks/mod.rs index b33f9f7..fdc9e35 100644 --- a/apps/recorder/src/models/subscriber_tasks/mod.rs +++ b/apps/recorder/src/models/subscriber_tasks/mod.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; -use sea_orm::entity::prelude::*; +use sea_orm::{ActiveValue, entity::prelude::*}; +use crate::task::SubscriberTaskTrait; pub use crate::task::{ SubscriberTask, SubscriberTaskType, SubscriberTaskTypeEnum, SubscriberTaskTypeVariant, SubscriberTaskTypeVariantIter, subscriber_task_schema, @@ -84,4 +85,19 @@ pub enum RelatedEntity { } #[async_trait] -impl ActiveModelBehavior for ActiveModel {} +impl ActiveModelBehavior for ActiveModel { + async fn before_save(mut self, _db: &C, _insert: bool) -> Result + where + C: ConnectionTrait, + { + if let ActiveValue::Set(subscriber_id) = self.subscriber_id + && let ActiveValue::Set(ref job) = self.job + && job.get_subscriber_id() != subscriber_id + { + return Err(DbErr::Custom( + "SubscriberTask subscriber_id does not match job.subscriber_id".to_string(), + )); + } + Ok(self) + } +} diff --git a/apps/recorder/src/task/registry/subscriber/base.rs b/apps/recorder/src/task/registry/subscriber/base.rs index d000e91..04ec593 100644 --- a/apps/recorder/src/task/registry/subscriber/base.rs +++ b/apps/recorder/src/task/registry/subscriber/base.rs @@ -6,7 +6,8 @@ macro_rules! register_subscriber_task_type { } ) => { $(#[$type_meta])* - #[derive(typed_builder::TypedBuilder, schemars::JsonSchema)] + #[derive(typed_builder::TypedBuilder, schemars::JsonSchema, ts_rs::TS)] + #[ts(export, rename_all = "camelCase")] $task_vis struct $task_name { $($(#[$field_meta])* pub $field_name: $field_type,)* pub subscriber_id: i32, diff --git a/apps/recorder/src/task/registry/subscriber/mod.rs b/apps/recorder/src/task/registry/subscriber/mod.rs index 92b2f2e..8aaf108 100644 --- a/apps/recorder/src/task/registry/subscriber/mod.rs +++ b/apps/recorder/src/task/registry/subscriber/mod.rs @@ -10,6 +10,7 @@ pub use subscription::{ SyncOneSubscriptionFeedsFullTask, SyncOneSubscriptionFeedsIncrementalTask, SyncOneSubscriptionSourcesTask, }; +use ts_rs::TS; macro_rules! register_subscriber_task_types { ( @@ -46,6 +47,7 @@ macro_rules! register_subscriber_task_types { $(#[$task_enum_meta])* #[serde(tag = "task_type")] + #[ts(export, rename_all = "camelCase", tag = "taskType")] pub enum $task_enum_name { $( $(#[$task_variant_meta])* @@ -136,7 +138,7 @@ register_subscriber_task_types!( } }, task_enum: { - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, FromJsonQueryResult, JsonSchema)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, FromJsonQueryResult, JsonSchema, TS)] pub enum SubscriberTask { SyncOneSubscriptionFeedsIncremental(SyncOneSubscriptionFeedsIncrementalTask), SyncOneSubscriptionFeedsFull(SyncOneSubscriptionFeedsFullTask), diff --git a/apps/recorder/tsconfig.json b/apps/recorder/tsconfig.json new file mode 100644 index 0000000..1a97792 --- /dev/null +++ b/apps/recorder/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "composite": true, + "module": "ESNext", + "moduleResolution": "bundler" + }, + "include": ["bindings"] +} diff --git a/apps/webui/package.json b/apps/webui/package.json index d87bca4..9249fcd 100644 --- a/apps/webui/package.json +++ b/apps/webui/package.json @@ -11,6 +11,7 @@ "codegen-watch": "graphql-codegen --config graphql-codegen.ts --watch" }, "dependencies": { + "recorder": "workspace:*", "@abraham/reflection": "^0.13.0", "@apollo/client": "^3.13.8", "@codemirror/language": "6.11.1", diff --git a/apps/webui/src/domains/recorder/schema/subscriptions.ts b/apps/webui/src/domains/recorder/schema/subscriptions.ts index ff34077..c9494a2 100644 --- a/apps/webui/src/domains/recorder/schema/subscriptions.ts +++ b/apps/webui/src/domains/recorder/schema/subscriptions.ts @@ -138,30 +138,6 @@ query GetSubscriptionDetail ($id: Int!) { } `; -export const SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL = gql` - mutation SyncSubscriptionFeedsIncremental($data: SubscriberTasksInsertInput!) { - subscriberTasksCreateOne(data: $data) { - id - } - } -`; - -export const SYNC_SUBSCRIPTION_FEEDS_FULL = gql` - mutation SyncSubscriptionFeedsFull($filter: SubscriptionsFilterInput!) { - subscriptionsSyncOneFeedsFull(filter: $filter) { - id - } - } -`; - -export const SYNC_SUBSCRIPTION_SOURCES = gql` - mutation SyncSubscriptionSources($filter: SubscriptionsFilterInput!) { - subscriptionsSyncOneSources(filter: $filter) { - id - } - } -`; - export const SubscriptionFormTypedMikanSeasonSchema = MikanSubscriptionSeasonSourceUrlSchema.and( type({ diff --git a/apps/webui/src/domains/recorder/schema/tasks.ts b/apps/webui/src/domains/recorder/schema/tasks.ts index aba920a..84fd0a2 100644 --- a/apps/webui/src/domains/recorder/schema/tasks.ts +++ b/apps/webui/src/domains/recorder/schema/tasks.ts @@ -1,10 +1,6 @@ -import { - type GetTasksQuery, - SubscriberTaskTypeEnum, -} from '@/infra/graphql/gql/graphql'; +import type { GetTasksQuery } from '@/infra/graphql/gql/graphql'; import { gql } from '@apollo/client'; -import { type } from 'arktype'; -import { SubscriptionSchema } from './subscriptions'; +import type { SubscriberTask } from 'recorder/bindings/SubscriberTask'; export const GET_TASKS = gql` query GetTasks($filter: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) { @@ -35,6 +31,14 @@ export const GET_TASKS = gql` } `; +export const INSERT_SUBSCRIBER_TASK = gql` + mutation InsertSubscriberTask($data: SubscriberTasksInsertInput!) { + subscriberTasksCreateOne(data: $data) { + id + } + } +`; + export const DELETE_TASKS = gql` mutation DeleteTasks($filter: SubscriberTasksFilterInput!) { subscriberTasksDelete(filter: $filter) @@ -60,27 +64,11 @@ export const RETRY_TASKS = gql` } `; -export const TaskTypedSyncOneSubscriptionFeedsIncrementalSchema = type({ - taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsIncremental}'`, -}).and(SubscriptionSchema); - -export const TaskTypedSyncOneSubscriptionFeedsFullSchema = type({ - taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsFull}'`, -}).and(SubscriptionSchema); - -export const TaskTypedSyncOneSubscriptionSourcesSchema = type({ - taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionSources}'`, -}).and(SubscriptionSchema); - -export const TaskTypedSchema = TaskTypedSyncOneSubscriptionFeedsFullSchema.or( - TaskTypedSyncOneSubscriptionFeedsIncrementalSchema -).or(TaskTypedSyncOneSubscriptionSourcesSchema); - -export type TaskTypedDto = typeof TaskTypedSchema.infer; +export type SubscriberTaskInsertDto = Omit; export type TaskDto = Omit< GetTasksQuery['subscriberTasks']['nodes'][number], 'job' > & { - job: TaskTypedDto; + job: SubscriberTask; }; diff --git a/apps/webui/src/infra/graphql/gql/gql.ts b/apps/webui/src/infra/graphql/gql/gql.ts index ba6c22c..f4b3c67 100644 --- a/apps/webui/src/infra/graphql/gql/gql.ts +++ b/apps/webui/src/infra/graphql/gql/gql.ts @@ -27,10 +27,8 @@ type Documents = { "\n mutation UpdateSubscriptions(\n $data: SubscriptionsUpdateInput!,\n $filter: SubscriptionsFilterInput!,\n ) {\n subscriptionsUpdate (\n data: $data\n filter: $filter\n ) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n }\n}\n": typeof types.UpdateSubscriptionsDocument, "\n mutation DeleteSubscriptions($filter: SubscriptionsFilterInput) {\n subscriptionsDelete(filter: $filter)\n }\n": typeof types.DeleteSubscriptionsDocument, "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filter: { id: {\n eq: $id\n } }) {\n nodes {\n id\n subscriberId\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n feed {\n nodes {\n id\n createdAt\n updatedAt\n token\n feedType\n feedSource\n }\n }\n subscriberTask {\n nodes {\n id\n taskType\n status\n }\n }\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n homepage\n }\n }\n }\n }\n}\n": typeof types.GetSubscriptionDetailDocument, - "\n mutation SyncSubscriptionFeedsIncremental($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n": typeof types.SyncSubscriptionFeedsIncrementalDocument, - "\n mutation SyncSubscriptionFeedsFull($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsFull(filter: $filter) {\n id\n }\n }\n": typeof types.SyncSubscriptionFeedsFullDocument, - "\n mutation SyncSubscriptionSources($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneSources(filter: $filter) {\n id\n }\n }\n": typeof types.SyncSubscriptionSourcesDocument, "\n query GetTasks($filter: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filter: $filter\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": typeof types.GetTasksDocument, + "\n mutation InsertSubscriberTask($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n": typeof types.InsertSubscriberTaskDocument, "\n mutation DeleteTasks($filter: SubscriberTasksFilterInput!) {\n subscriberTasksDelete(filter: $filter)\n }\n": typeof types.DeleteTasksDocument, "\n mutation RetryTasks($filter: SubscriberTasksFilterInput!) {\n subscriberTasksRetryOne(filter: $filter) {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n }\n": typeof types.RetryTasksDocument, }; @@ -48,10 +46,8 @@ const documents: Documents = { "\n mutation UpdateSubscriptions(\n $data: SubscriptionsUpdateInput!,\n $filter: SubscriptionsFilterInput!,\n ) {\n subscriptionsUpdate (\n data: $data\n filter: $filter\n ) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n }\n}\n": types.UpdateSubscriptionsDocument, "\n mutation DeleteSubscriptions($filter: SubscriptionsFilterInput) {\n subscriptionsDelete(filter: $filter)\n }\n": types.DeleteSubscriptionsDocument, "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filter: { id: {\n eq: $id\n } }) {\n nodes {\n id\n subscriberId\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n feed {\n nodes {\n id\n createdAt\n updatedAt\n token\n feedType\n feedSource\n }\n }\n subscriberTask {\n nodes {\n id\n taskType\n status\n }\n }\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n homepage\n }\n }\n }\n }\n}\n": types.GetSubscriptionDetailDocument, - "\n mutation SyncSubscriptionFeedsIncremental($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n": types.SyncSubscriptionFeedsIncrementalDocument, - "\n mutation SyncSubscriptionFeedsFull($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsFull(filter: $filter) {\n id\n }\n }\n": types.SyncSubscriptionFeedsFullDocument, - "\n mutation SyncSubscriptionSources($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneSources(filter: $filter) {\n id\n }\n }\n": types.SyncSubscriptionSourcesDocument, "\n query GetTasks($filter: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filter: $filter\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": types.GetTasksDocument, + "\n mutation InsertSubscriberTask($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n": types.InsertSubscriberTaskDocument, "\n mutation DeleteTasks($filter: SubscriberTasksFilterInput!) {\n subscriberTasksDelete(filter: $filter)\n }\n": types.DeleteTasksDocument, "\n mutation RetryTasks($filter: SubscriberTasksFilterInput!) {\n subscriberTasksRetryOne(filter: $filter) {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n }\n": types.RetryTasksDocument, }; @@ -122,22 +118,14 @@ export function gql(source: "\n mutation DeleteSubscriptions($filter: Subscri * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql(source: "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filter: { id: {\n eq: $id\n } }) {\n nodes {\n id\n subscriberId\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n feed {\n nodes {\n id\n createdAt\n updatedAt\n token\n feedType\n feedSource\n }\n }\n subscriberTask {\n nodes {\n id\n taskType\n status\n }\n }\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n homepage\n }\n }\n }\n }\n}\n"): (typeof documents)["\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filter: { id: {\n eq: $id\n } }) {\n nodes {\n id\n subscriberId\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n feed {\n nodes {\n id\n createdAt\n updatedAt\n token\n feedType\n feedSource\n }\n }\n subscriberTask {\n nodes {\n id\n taskType\n status\n }\n }\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n homepage\n }\n }\n }\n }\n}\n"]; -/** - * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function gql(source: "\n mutation SyncSubscriptionFeedsIncremental($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsIncremental($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n"]; -/** - * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function gql(source: "\n mutation SyncSubscriptionFeedsFull($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsFull(filter: $filter) {\n id\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsFull($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsFull(filter: $filter) {\n id\n }\n }\n"]; -/** - * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function gql(source: "\n mutation SyncSubscriptionSources($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneSources(filter: $filter) {\n id\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionSources($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneSources(filter: $filter) {\n id\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql(source: "\n query GetTasks($filter: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filter: $filter\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n"): (typeof documents)["\n query GetTasks($filter: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filter: $filter\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "\n mutation InsertSubscriberTask($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n"): (typeof documents)["\n mutation InsertSubscriberTask($data: SubscriberTasksInsertInput!) {\n subscriberTasksCreateOne(data: $data) {\n id\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/apps/webui/src/infra/graphql/gql/graphql.ts b/apps/webui/src/infra/graphql/gql/graphql.ts index f125a52..25054a8 100644 --- a/apps/webui/src/infra/graphql/gql/graphql.ts +++ b/apps/webui/src/infra/graphql/gql/graphql.ts @@ -419,7 +419,6 @@ export type CronFilterInput = { export type CronInsertInput = { cronExpr: Scalars['String']['input']; enabled?: InputMaybe; - id?: InputMaybe; maxAttempts?: InputMaybe; subscriberTask?: InputMaybe; timeoutMs?: InputMaybe; @@ -2183,27 +2182,6 @@ export type GetSubscriptionDetailQueryVariables = Exact<{ export type GetSubscriptionDetailQuery = { __typename?: 'Query', subscriptions: { __typename?: 'SubscriptionsConnection', nodes: Array<{ __typename?: 'Subscriptions', id: number, subscriberId: number, displayName: string, createdAt: string, updatedAt: string, category: SubscriptionCategoryEnum, sourceUrl: string, enabled: boolean, feed: { __typename?: 'FeedsConnection', nodes: Array<{ __typename?: 'Feeds', id: number, createdAt: string, updatedAt: string, token: string, feedType: FeedTypeEnum, feedSource: FeedSourceEnum }> }, subscriberTask: { __typename?: 'SubscriberTasksConnection', nodes: Array<{ __typename?: 'SubscriberTasks', id: string, taskType: SubscriberTaskTypeEnum, status: SubscriberTaskStatusEnum }> }, credential3rd?: { __typename?: 'Credential3rd', id: number, username?: string | null } | null, bangumi: { __typename?: 'BangumiConnection', nodes: Array<{ __typename?: 'Bangumi', createdAt: string, updatedAt: string, id: number, mikanBangumiId?: string | null, displayName: string, season: number, seasonRaw?: string | null, fansub?: string | null, mikanFansubId?: string | null, rssLink?: string | null, posterLink?: string | null, homepage?: string | null }> } }> } }; -export type SyncSubscriptionFeedsIncrementalMutationVariables = Exact<{ - data: SubscriberTasksInsertInput; -}>; - - -export type SyncSubscriptionFeedsIncrementalMutation = { __typename?: 'Mutation', subscriberTasksCreateOne: { __typename?: 'SubscriberTasksBasic', id: string } }; - -export type SyncSubscriptionFeedsFullMutationVariables = Exact<{ - filter: SubscriptionsFilterInput; -}>; - - -export type SyncSubscriptionFeedsFullMutation = { __typename?: 'Mutation', subscriptionsSyncOneFeedsFull: { __typename?: 'SubscriberTasksBasic', id: string } }; - -export type SyncSubscriptionSourcesMutationVariables = Exact<{ - filter: SubscriptionsFilterInput; -}>; - - -export type SyncSubscriptionSourcesMutation = { __typename?: 'Mutation', subscriptionsSyncOneSources: { __typename?: 'SubscriberTasksBasic', id: string } }; - export type GetTasksQueryVariables = Exact<{ filter: SubscriberTasksFilterInput; orderBy: SubscriberTasksOrderInput; @@ -2213,6 +2191,13 @@ export type GetTasksQueryVariables = Exact<{ export type GetTasksQuery = { __typename?: 'Query', subscriberTasks: { __typename?: 'SubscriberTasksConnection', nodes: Array<{ __typename?: 'SubscriberTasks', id: string, job: any, taskType: SubscriberTaskTypeEnum, status: SubscriberTaskStatusEnum, attempts: number, maxAttempts: number, runAt: string, lastError?: string | null, lockAt?: string | null, lockBy?: string | null, doneAt?: string | null, priority: number }>, paginationInfo?: { __typename?: 'PaginationInfo', total: number, pages: number } | null } }; +export type InsertSubscriberTaskMutationVariables = Exact<{ + data: SubscriberTasksInsertInput; +}>; + + +export type InsertSubscriberTaskMutation = { __typename?: 'Mutation', subscriberTasksCreateOne: { __typename?: 'SubscriberTasksBasic', id: string } }; + export type DeleteTasksMutationVariables = Exact<{ filter: SubscriberTasksFilterInput; }>; @@ -2241,9 +2226,7 @@ export const InsertSubscriptionDocument = {"kind":"Document","definitions":[{"ki export const UpdateSubscriptionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSubscriptions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsUpdateInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsUpdate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}}]}}]} as unknown as DocumentNode; export const DeleteSubscriptionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteSubscriptions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}]}]}}]} as unknown as DocumentNode; export const GetSubscriptionDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSubscriptionDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"subscriberId"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"feed"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"feedType"}},{"kind":"Field","name":{"kind":"Name","value":"feedSource"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscriberTask"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"taskType"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"credential3rd"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bangumi"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"mikanBangumiId"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"seasonRaw"}},{"kind":"Field","name":{"kind":"Name","value":"fansub"}},{"kind":"Field","name":{"kind":"Name","value":"mikanFansubId"}},{"kind":"Field","name":{"kind":"Name","value":"rssLink"}},{"kind":"Field","name":{"kind":"Name","value":"posterLink"}},{"kind":"Field","name":{"kind":"Name","value":"homepage"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const SyncSubscriptionFeedsIncrementalDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionFeedsIncremental"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasksCreateOne"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; -export const SyncSubscriptionFeedsFullDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionFeedsFull"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsSyncOneFeedsFull"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; -export const SyncSubscriptionSourcesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionSources"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsSyncOneSources"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const GetTasksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTasks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksFilterInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksOrderInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"job"}},{"kind":"Field","name":{"kind":"Name","value":"taskType"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"attempts"}},{"kind":"Field","name":{"kind":"Name","value":"maxAttempts"}},{"kind":"Field","name":{"kind":"Name","value":"runAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastError"}},{"kind":"Field","name":{"kind":"Name","value":"lockAt"}},{"kind":"Field","name":{"kind":"Name","value":"lockBy"}},{"kind":"Field","name":{"kind":"Name","value":"doneAt"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}}]}},{"kind":"Field","name":{"kind":"Name","value":"paginationInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}}]}}]}}]} as unknown as DocumentNode; +export const InsertSubscriberTaskDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InsertSubscriberTask"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasksCreateOne"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const DeleteTasksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteTasks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasksDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}]}]}}]} as unknown as DocumentNode; export const RetryTasksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RetryTasks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasksRetryOne"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"job"}},{"kind":"Field","name":{"kind":"Name","value":"taskType"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"attempts"}},{"kind":"Field","name":{"kind":"Name","value":"maxAttempts"}},{"kind":"Field","name":{"kind":"Name","value":"runAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastError"}},{"kind":"Field","name":{"kind":"Name","value":"lockAt"}},{"kind":"Field","name":{"kind":"Name","value":"lockBy"}},{"kind":"Field","name":{"kind":"Name","value":"doneAt"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx b/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx index 9d36cab..3015fbd 100644 --- a/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx +++ b/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx @@ -6,19 +6,11 @@ import { DialogTitle, } from '@/components/ui/dialog'; import { Spinner } from '@/components/ui/spinner'; +import { INSERT_SUBSCRIBER_TASK } from '@/domains/recorder/schema/tasks'; import { - SYNC_SUBSCRIPTION_FEEDS_FULL, - SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL, - SYNC_SUBSCRIPTION_SOURCES, -} from '@/domains/recorder/schema/subscriptions'; -import { + type InsertSubscriberTaskMutation, + type InsertSubscriberTaskMutationVariables, SubscriberTaskTypeEnum, - type SyncSubscriptionFeedsFullMutation, - type SyncSubscriptionFeedsFullMutationVariables, - type SyncSubscriptionFeedsIncrementalMutation, - type SyncSubscriptionFeedsIncrementalMutationVariables, - type SyncSubscriptionSourcesMutation, - type SyncSubscriptionSourcesMutationVariables, } from '@/infra/graphql/gql/graphql'; import { useMutation } from '@apollo/client'; import { useNavigate } from '@tanstack/react-router'; @@ -37,29 +29,13 @@ export interface SubscriptionSyncViewProps { export const SubscriptionSyncView = memo( ({ id, onComplete }: SubscriptionSyncViewProps) => { - const [syncSubscriptionFeedsIncremental, { loading: loadingIncremental }] = - useMutation< - SyncSubscriptionFeedsIncrementalMutation, - SyncSubscriptionFeedsIncrementalMutationVariables - >(SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL, { - onCompleted: (data) => { - toast.success('Sync completed'); - onComplete(data.subscriberTasksCreateOne); - }, - onError: (error) => { - toast.error('Failed to sync subscription', { - description: error.message, - }); - }, - }); - - const [syncSubscriptionFeedsFull, { loading: loadingFull }] = useMutation< - SyncSubscriptionFeedsFullMutation, - SyncSubscriptionFeedsFullMutationVariables - >(SYNC_SUBSCRIPTION_FEEDS_FULL, { + const [insertSubscriberTask, { loading: loadingInsert }] = useMutation< + InsertSubscriberTaskMutation, + InsertSubscriberTaskMutationVariables + >(INSERT_SUBSCRIBER_TASK, { onCompleted: (data) => { toast.success('Sync completed'); - onComplete(data.subscriptionsSyncOneFeedsFull); + onComplete(data.subscriberTasksCreateOne); }, onError: (error) => { toast.error('Failed to sync subscription', { @@ -68,22 +44,7 @@ export const SubscriptionSyncView = memo( }, }); - const [syncSubscriptionSources, { loading: loadingSources }] = useMutation< - SyncSubscriptionSourcesMutation, - SyncSubscriptionSourcesMutationVariables - >(SYNC_SUBSCRIPTION_SOURCES, { - onCompleted: (data) => { - toast.success('Sync completed'); - onComplete(data.subscriptionsSyncOneSources); - }, - onError: (error) => { - toast.error('Failed to sync subscription', { - description: error.message, - }); - }, - }); - - const loading = loadingIncremental || loadingFull || loadingSources; + const loading = loadingInsert; return (
@@ -91,8 +52,15 @@ export const SubscriptionSyncView = memo( size="lg" variant="outline" onClick={() => - syncSubscriptionSources({ - variables: { filter: { id: { eq: id } } }, + insertSubscriberTask({ + variables: { + data: { + job: { + subscriptionId: id, + taskType: SubscriberTaskTypeEnum.SyncOneSubscriptionSources, + }, + }, + }, }) } > @@ -103,11 +71,11 @@ export const SubscriptionSyncView = memo( size="lg" variant="outline" onClick={() => - syncSubscriptionFeedsIncremental({ + insertSubscriberTask({ variables: { data: { job: { - subscriberId: id, + subscriptionId: id, taskType: SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsIncremental, }, @@ -123,8 +91,16 @@ export const SubscriptionSyncView = memo( size="lg" variant="outline" onClick={() => - syncSubscriptionFeedsFull({ - variables: { filter: { id: { eq: id } } }, + insertSubscriberTask({ + variables: { + data: { + job: { + subscriptionId: id, + taskType: + SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsFull, + }, + }, + }, }) } > diff --git a/apps/webui/tsconfig.json b/apps/webui/tsconfig.json index 40dccc8..31d873c 100644 --- a/apps/webui/tsconfig.json +++ b/apps/webui/tsconfig.json @@ -11,5 +11,10 @@ "@/*": ["./src/*"] } }, - "include": ["src"] + "include": ["src"], + "references": [ + { + "path": "../../apps/recorder" + } + ] } diff --git a/justfile b/justfile index d64d184..6ef6bb6 100644 --- a/justfile +++ b/justfile @@ -11,8 +11,8 @@ prepare-dev-testcontainers: docker pull ghcr.io/dumtruck/konobangu-testing-torrents:latest docker pull postgres:17-alpine -dev-optimize-images: - npx -y zx apps/recorder/examples/optimize_image.mjs +export-recorder-ts-bindings: + cargo test export_bindings -p recorder dev-webui: pnpm run --filter=webui dev diff --git a/packages/util-derive/src/lib.rs b/packages/util-derive/src/lib.rs index 46daf04..e7020e2 100644 --- a/packages/util-derive/src/lib.rs +++ b/packages/util-derive/src/lib.rs @@ -2,10 +2,15 @@ extern crate proc_macro; use convert_case::{Case, Casing}; use darling::{FromDeriveInput, FromField, ast::Data, util::Ignored}; +use heck::ToLowerCamelCase; use proc_macro::TokenStream; -use quote::{format_ident, quote}; +use proc_macro_crate::{FoundCrate, crate_name}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned}; use syn::{Attribute, DeriveInput, Generics, Ident, parse_macro_input}; +use crate::derives::attributes::related_attr; + #[derive(snafu::Snafu, Debug)] enum GeneratorError { #[snafu(transparent)] @@ -160,3 +165,135 @@ pub fn derive_dynamic_graphql(input: TokenStream) -> TokenStream { Err(err) => err.write_errors().into(), } } + +enum Error { + InputNotEnum, + InvalidEntityPath, + Syn(syn::Error), +} + +struct DeriveRelatedEntity { + entity_ident: TokenStream, + ident: syn::Ident, + variants: syn::punctuated::Punctuated, +} + +impl DeriveRelatedEntity { + fn new(input: syn::DeriveInput) -> Result { + let sea_attr = related_attr::SeaOrm::try_from_attributes(&input.attrs) + .map_err(Error::Syn)? + .unwrap_or_default(); + + let ident = input.ident; + let entity_ident = match sea_attr.entity.as_ref().map(Self::parse_lit_string) { + Some(entity_ident) => entity_ident.map_err(|_| Error::InvalidEntityPath)?, + None => quote! { Entity }, + }; + + let variants = match input.data { + syn::Data::Enum(syn::DataEnum { variants, .. }) => variants, + _ => return Err(Error::InputNotEnum), + }; + + Ok(DeriveRelatedEntity { + entity_ident, + ident, + variants, + }) + } + + fn expand(&self) -> syn::Result { + let ident = &self.ident; + let entity_ident = &self.entity_ident; + + let variant_implementations: Vec = self + .variants + .iter() + .map(|variant| { + let attr = related_attr::SeaOrm::from_attributes(&variant.attrs)?; + + let enum_name = &variant.ident; + + let target_entity = attr + .entity + .as_ref() + .map(Self::parse_lit_string) + .ok_or_else(|| { + syn::Error::new_spanned(variant, "Missing value for 'entity'") + })??; + + let def = match attr.def { + Some(def) => Some(Self::parse_lit_string(&def).map_err(|_| { + syn::Error::new_spanned(variant, "Missing value for 'def'") + })?), + None => None, + }; + + let name = enum_name.to_string().to_lower_camel_case(); + + if let Some(def) = def { + Result::<_, syn::Error>::Ok(quote! { + Self::#enum_name => builder.get_relation::<#entity_ident, #target_entity>(#name, #def) + }) + } else { + Result::<_, syn::Error>::Ok(quote! { + Self::#enum_name => via_builder.get_relation::<#entity_ident, #target_entity>(#name) + }) + } + + }) + .collect::, _>>()?; + + // Get the path of the `async-graphql` on the application's Cargo.toml + let async_graphql_crate = match crate_name("async-graphql") { + // if found, use application's `async-graphql` + Ok(FoundCrate::Name(name)) => { + let ident = Ident::new(&name, Span::call_site()); + quote! { #ident } + } + Ok(FoundCrate::Itself) => quote! { async_graphql }, + // if not, then use the `async-graphql` re-exported by `seaography` + Err(_) => quote! { seaography::async_graphql }, + }; + + Ok(quote! { + impl seaography::RelationBuilder for #ident { + fn get_relation(&self, context: & 'static seaography::BuilderContext) -> #async_graphql_crate::dynamic::Field { + let builder = seaography::EntityObjectRelationBuilder { context }; + let via_builder = seaography::EntityObjectViaRelationBuilder { context }; + match self { + #(#variant_implementations,)* + _ => panic!("No relations for this entity"), + } + } + + } + }) + } + + fn parse_lit_string(lit: &syn::Lit) -> syn::Result { + match lit { + syn::Lit::Str(lit_str) => lit_str + .value() + .parse() + .map_err(|_| syn::Error::new_spanned(lit, "attribute not valid")), + _ => Err(syn::Error::new_spanned(lit, "attribute must be a string")), + } + } +} + +/// Method to derive a Related enumeration +fn expand_derive_related_entity(input: syn::DeriveInput) -> syn::Result { + let ident_span = input.ident.span(); + + match DeriveRelatedEntity::new(input) { + Ok(model) => model.expand(), + Err(Error::InputNotEnum) => Ok(quote_spanned! { + ident_span => compile_error!("you can only derive DeriveRelation on enums"); + }), + Err(Error::InvalidEntityPath) => Ok(quote_spanned! { + ident_span => compile_error!("invalid attribute value for 'entity'"); + }), + Err(Error::Syn(err)) => Err(err), + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31e2966..99e4e78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,6 +67,8 @@ importers: specifier: ^2.9.99 version: 2.9.99 + apps/recorder: {} + apps/webui: dependencies: '@abraham/reflection': @@ -249,6 +251,9 @@ importers: recharts: specifier: ^2.15.3 version: 2.15.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + recorder: + specifier: workspace:* + version: link:../recorder rxjs: specifier: ^7.8.2 version: 7.8.2 diff --git a/tsconfig.json b/tsconfig.json index 5cacb2f..9616a0b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,9 +4,13 @@ { "path": "./apps/email-playground" }, + { + "path": "./apps/recorder" + }, { "path": "./apps/webui" }, + { "path": "./packages/email" },