From 421f9d0293a0b992c254303d874b1ce47da0dd9f Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Mon, 16 Jun 2025 07:56:52 +0800 Subject: [PATCH] feat: task ui & custom filter mutation --- apps/recorder/src/app/core.rs | 25 +- apps/recorder/src/errors/app_error.rs | 4 +- .../src/graphql/domains/subscriber_tasks.rs | 104 +++++-- .../src/graphql/domains/subscriptions.rs | 293 ++++++++---------- apps/recorder/src/graphql/infra/custom.rs | 73 +++-- apps/recorder/src/models/auth.rs | 4 +- apps/recorder/src/models/subscriptions.rs | 14 +- apps/recorder/src/task/db.rs | 7 - apps/recorder/src/task/extern.rs | 16 + apps/recorder/src/task/mod.rs | 4 +- apps/recorder/src/task/service.rs | 41 ++- .../domains/recorder/schema/subscriptions.ts | 18 +- .../src/domains/recorder/schema/tasks.ts | 25 ++ apps/webui/src/infra/graphql/gql/gql.ts | 30 +- apps/webui/src/infra/graphql/gql/graphql.ts | 153 +++------ .../routes/_app/subscriptions/-sync.tsx | 26 +- .../routes/_app/subscriptions/manage.tsx | 51 ++- .../routes/_app/tasks/-actions.tsx | 5 - .../routes/_app/tasks/detail.$id.tsx | 46 ++- .../presentation/routes/_app/tasks/manage.tsx | 89 +++++- 20 files changed, 594 insertions(+), 434 deletions(-) delete mode 100644 apps/recorder/src/task/db.rs create mode 100644 apps/recorder/src/task/extern.rs delete mode 100644 apps/webui/src/presentation/routes/_app/tasks/-actions.tsx diff --git a/apps/recorder/src/app/core.rs b/apps/recorder/src/app/core.rs index a230618..9cfaf63 100644 --- a/apps/recorder/src/app/core.rs +++ b/apps/recorder/src/app/core.rs @@ -6,6 +6,7 @@ use tracing::instrument; use super::{builder::AppBuilder, context::AppContextTrait}; use crate::{ + app::Environment, errors::{RecorderError, RecorderResult}, web::{ controller::{self, core::ControllerTrait}, @@ -72,7 +73,6 @@ impl App { .into_make_service_with_connect_info::(); let task = context.task(); - tokio::try_join!( async { axum::serve(listener, router) @@ -84,15 +84,20 @@ impl App { Ok::<(), RecorderError>(()) }, async { - let monitor = task.setup_monitor().await?; - - monitor - .run_with_signal(async move { - Self::shutdown_signal().await; - tracing::info!("apalis shutting down..."); - Ok(()) - }) - .await?; + { + let monitor = task.setup_monitor().await?; + if matches!(context.environment(), Environment::Development) { + monitor.run().await?; + } else { + monitor + .run_with_signal(async move { + Self::shutdown_signal().await; + tracing::info!("apalis shutting down..."); + Ok(()) + }) + .await?; + } + } Ok::<(), RecorderError>(()) }, diff --git a/apps/recorder/src/errors/app_error.rs b/apps/recorder/src/errors/app_error.rs index dc9bd18..7561ee2 100644 --- a/apps/recorder/src/errors/app_error.rs +++ b/apps/recorder/src/errors/app_error.rs @@ -103,7 +103,7 @@ pub enum RecorderError { #[snafu(source(from(Box, OptDynErr::some)))] source: OptDynErr, }, - #[snafu(display("Model Entity {entity} not found"))] + #[snafu(display("Model Entity {entity} not found or not belong to subscriber"))] ModelEntityNotFound { entity: Cow<'static, str> }, #[snafu(transparent)] FetchError { source: FetchError }, @@ -123,6 +123,8 @@ pub enum RecorderError { #[snafu(source(from(Box, OptDynErr::some)))] source: OptDynErr, }, + #[snafu(display("Invalid task id: {message}"))] + InvalidTaskId { message: String }, } impl RecorderError { diff --git a/apps/recorder/src/graphql/domains/subscriber_tasks.rs b/apps/recorder/src/graphql/domains/subscriber_tasks.rs index 4c81bc5..0ada662 100644 --- a/apps/recorder/src/graphql/domains/subscriber_tasks.rs +++ b/apps/recorder/src/graphql/domains/subscriber_tasks.rs @@ -1,35 +1,39 @@ -use std::{ops::Deref, pin::Pin, sync::Arc}; +use std::{ops::Deref, sync::Arc}; -use async_graphql::dynamic::{ResolverContext, ValueAccessor}; +use async_graphql::dynamic::{FieldValue, TypeRef}; use sea_orm::{ - ConnectionTrait, EntityTrait, QueryFilter, QuerySelect, QueryTrait, prelude::Expr, + ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QuerySelect, QueryTrait, prelude::Expr, sea_query::Query, }; -use seaography::{Builder as SeaographyBuilder, BuilderContext, get_filter_conditions}; +use seaography::{ + Builder as SeaographyBuilder, BuilderContext, EntityDeleteMutationBuilder, EntityObjectBuilder, + EntityQueryFieldBuilder, get_filter_conditions, +}; use crate::{ - app::AppContextTrait, - errors::{RecorderError, RecorderResult}, + errors::RecorderError, graphql::{ domains::subscribers::restrict_subscriber_for_entity, infra::{ - custom::generate_custom_entity_delete_mutation_field, + custom::generate_entity_filter_mutation_field, json::{convert_jsonb_output_case_for_entity, restrict_jsonb_filter_input_for_entity}, }, }, models::subscriber_tasks, - task::ApalisJob, + task::{ApalisJobs, ApalisSchema}, }; -pub fn register_subscriber_tasks_entity_mutations(builder: &mut SeaographyBuilder) { +pub fn register_subscriber_tasks_entity_mutations( + mut builder: SeaographyBuilder, +) -> SeaographyBuilder { let context = builder.context; - let delete_mutation = generate_custom_entity_delete_mutation_field::( - context, - Arc::new( - |resolver_ctx: &ResolverContext<'_>, - app_ctx: Arc, - filters: Option>| - -> Pin>> + Send>> { + { + let entitity_delete_mutation_builder = EntityDeleteMutationBuilder { context }; + let delete_mutation = generate_entity_filter_mutation_field::( + context, + entitity_delete_mutation_builder.type_name::(), + TypeRef::named_nn(TypeRef::INT), + Arc::new(|resolver_ctx, app_ctx, filters| { let filters_condition = get_filter_conditions::( resolver_ctx, context, @@ -44,23 +48,74 @@ pub fn register_subscriber_tasks_entity_mutations(builder: &mut SeaographyBuilde .filter(filters_condition); let delete_query = Query::delete() - .from_table(ApalisJob::Table) + .from_table((ApalisSchema::Schema, ApalisJobs::Table)) .and_where( - Expr::col(ApalisJob::Id).in_subquery(select_subquery.into_query()), + Expr::col(ApalisJobs::Id).in_subquery(select_subquery.into_query()), ) .to_owned(); let db_backend = db.deref().get_database_backend(); let delete_statement = db_backend.build(&delete_query); + let result = db.execute(delete_statement).await?; - Ok::, RecorderError>(Some(result.rows_affected() as i32)) + Ok::<_, RecorderError>(Some(FieldValue::value(result.rows_affected() as i32))) }) - as Pin>> + Send>> - }, - ), - ); - builder.mutations.push(delete_mutation); + }), + ); + builder.mutations.push(delete_mutation); + } + { + let entity_object_builder = EntityObjectBuilder { context }; + let entity_query_field = EntityQueryFieldBuilder { context }; + let entity_retry_one_mutation_name = format!( + "{}RetryOne", + entity_query_field.type_name::() + ); + let retry_one_mutation = + generate_entity_filter_mutation_field::( + context, + entity_retry_one_mutation_name, + TypeRef::named_nn(entity_object_builder.type_name::()), + Arc::new(|resolver_ctx, app_ctx, filters| { + let filters_condition = get_filter_conditions::( + resolver_ctx, + context, + filters, + ); + Box::pin(async move { + let db = app_ctx.db(); + + let job_id = subscriber_tasks::Entity::find() + .filter(filters_condition) + .select_only() + .column(subscriber_tasks::Column::Id) + .into_tuple::() + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "SubscriberTask".into(), + })?; + + let task = app_ctx.task(); + task.retry_subscriber_task(job_id.clone()).await?; + + let task_model = subscriber_tasks::Entity::find() + .filter(subscriber_tasks::Column::Id.eq(&job_id)) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "SubscriberTask".into(), + })?; + + Ok::<_, RecorderError>(Some(FieldValue::owned_any(task_model))) + }) + }), + ); + builder.mutations.push(retry_one_mutation); + } + + builder } pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext) { @@ -89,6 +144,7 @@ pub fn register_subscriber_tasks_to_schema_builder( builder = builder.register_entity_dataloader_one_to_one(subscriber_tasks::Entity, tokio::spawn); builder = builder.register_entity_dataloader_one_to_many(subscriber_tasks::Entity, tokio::spawn); + builder = register_subscriber_tasks_entity_mutations(builder); builder.register_enumeration::(); builder.register_enumeration::(); builder diff --git a/apps/recorder/src/graphql/domains/subscriptions.rs b/apps/recorder/src/graphql/domains/subscriptions.rs index 25b7fcf..1e653a9 100644 --- a/apps/recorder/src/graphql/domains/subscriptions.rs +++ b/apps/recorder/src/graphql/domains/subscriptions.rs @@ -1,104 +1,58 @@ use std::sync::Arc; -use async_graphql::dynamic::{ - Field, FieldFuture, FieldValue, InputObject, InputValue, Object, TypeRef, +use async_graphql::dynamic::{FieldValue, TypeRef}; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; +use seaography::{ + Builder as SeaographyBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, + get_filter_conditions, }; -use seaography::Builder as SeaographyBuilder; -use serde::{Deserialize, Serialize}; -use util_derive::DynamicGraphql; use crate::{ - app::AppContextTrait, - auth::AuthUserInfo, - models::subscriptions::{self, SubscriptionTrait}, + errors::RecorderError, + graphql::infra::custom::generate_entity_filter_mutation_field, + models::{ + subscriber_tasks, + subscriptions::{self, SubscriptionTrait}, + }, task::SubscriberTask, }; -#[derive(DynamicGraphql, Serialize, Deserialize, Clone, Debug)] -struct SyncOneSubscriptionFilterInput { - pub id: i32, -} - -impl SyncOneSubscriptionFilterInput { - fn input_type_name() -> &'static str { - "SyncOneSubscriptionFilterInput" - } - - fn arg_name() -> &'static str { - "filter" - } - - fn generate_input_object() -> InputObject { - InputObject::new(Self::input_type_name()) - .description("The input of the subscriptionSyncOne series of mutations") - .field(InputValue::new( - SyncOneSubscriptionFilterInputFieldEnum::Id.as_str(), - TypeRef::named_nn(TypeRef::INT), - )) - } -} - -#[derive(DynamicGraphql, Serialize, Deserialize, Clone, Debug)] -pub struct SyncOneSubscriptionInfo { - pub task_id: String, -} - -impl SyncOneSubscriptionInfo { - fn object_type_name() -> &'static str { - "SyncOneSubscriptionInfo" - } - - fn generate_output_object() -> Object { - Object::new(Self::object_type_name()) - .description("The output of the subscriptionSyncOne series of mutations") - .field(Field::new( - SyncOneSubscriptionInfoFieldEnum::TaskId, - TypeRef::named_nn(TypeRef::STRING), - move |ctx| { - FieldFuture::new(async move { - let subscription_info = ctx.parent_value.try_downcast_ref::()?; - Ok(Some(async_graphql::Value::from( - subscription_info.task_id.as_str(), - ))) - }) - }, - )) - } -} - pub fn register_subscriptions_to_schema_builder( mut builder: SeaographyBuilder, ) -> SeaographyBuilder { - builder.schema = builder - .schema - .register(SyncOneSubscriptionFilterInput::generate_input_object()); - builder.schema = builder - .schema - .register(SyncOneSubscriptionInfo::generate_output_object()); + let context = builder.context; - builder.mutations.push( - Field::new( - "subscriptionSyncOneFeedsIncremental", - TypeRef::named_nn(SyncOneSubscriptionInfo::object_type_name()), - move |ctx| { - FieldFuture::new(async move { - let auth_user_info = ctx.data::()?; + let entity_object_builder = EntityObjectBuilder { context }; + let entity_query_field = EntityQueryFieldBuilder { context }; - let app_ctx = ctx.data::>()?; - let subscriber_id = auth_user_info.subscriber_auth.subscriber_id; + { + let sync_one_feeds_incremental_mutation_name = format!( + "{}SyncOneFeedsIncremental", + entity_query_field.type_name::() + ); - let filter_input: SyncOneSubscriptionFilterInput = ctx - .args - .get(SyncOneSubscriptionFilterInput::arg_name()) - .unwrap() - .deserialize()?; + let sync_one_feeds_incremental_mutation = generate_entity_filter_mutation_field::< + subscriptions::Entity, + _, + _, + >( + builder.context, + sync_one_feeds_incremental_mutation_name, + TypeRef::named_nn(entity_object_builder.type_name::()), + Arc::new(|resolver_ctx, app_ctx, filters| { + let filters_condition = + get_filter_conditions::(resolver_ctx, context, filters); - let subscription_model = subscriptions::Model::find_by_id_and_subscriber_id( - app_ctx.as_ref(), - filter_input.id, - subscriber_id, - ) - .await?; + Box::pin(async move { + let db = app_ctx.db(); + + let subscription_model = subscriptions::Entity::find() + .filter(filters_condition) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "Subscription".into(), + })?; let subscription = subscriptions::Subscription::try_from_model(&subscription_model)?; @@ -107,48 +61,56 @@ pub fn register_subscriptions_to_schema_builder( let task_id = task_service .add_subscriber_task( - auth_user_info.subscriber_auth.subscriber_id, + subscription_model.subscriber_id, SubscriberTask::SyncOneSubscriptionFeedsIncremental( subscription.into(), ), ) .await?; - Ok(Some(FieldValue::owned_any(SyncOneSubscriptionInfo { - task_id: task_id.to_string(), - }))) + let task_model = subscriber_tasks::Entity::find() + .filter(subscriber_tasks::Column::Id.eq(task_id.to_string())) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "SubscriberTask".into(), + })?; + + Ok(Some(FieldValue::owned_any(task_model))) }) - }, - ) - .argument(InputValue::new( - SyncOneSubscriptionFilterInput::arg_name(), - TypeRef::named_nn(SyncOneSubscriptionFilterInput::input_type_name()), - )), - ); + }), + ); - builder.mutations.push( - Field::new( - "subscriptionSyncOneFeedsFull", - TypeRef::named_nn(SyncOneSubscriptionInfo::object_type_name()), - move |ctx| { - FieldFuture::new(async move { - let auth_user_info = ctx.data::()?; + builder.mutations.push(sync_one_feeds_incremental_mutation); + } + { + let sync_one_feeds_full_mutation_name = format!( + "{}SyncOneFeedsFull", + entity_query_field.type_name::() + ); - let app_ctx = ctx.data::>()?; - let subscriber_id = auth_user_info.subscriber_auth.subscriber_id; + let sync_one_feeds_full_mutation = generate_entity_filter_mutation_field::< + subscriptions::Entity, + _, + _, + >( + builder.context, + sync_one_feeds_full_mutation_name, + TypeRef::named_nn(entity_object_builder.type_name::()), + Arc::new(|resolver_ctx, app_ctx, filters| { + let filters_condition = + get_filter_conditions::(resolver_ctx, context, filters); - let filter_input: SyncOneSubscriptionFilterInput = ctx - .args - .get(SyncOneSubscriptionFilterInput::arg_name()) - .unwrap() - .deserialize()?; + Box::pin(async move { + let db = app_ctx.db(); - let subscription_model = subscriptions::Model::find_by_id_and_subscriber_id( - app_ctx.as_ref(), - filter_input.id, - subscriber_id, - ) - .await?; + let subscription_model = subscriptions::Entity::find() + .filter(filters_condition) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "Subscription".into(), + })?; let subscription = subscriptions::Subscription::try_from_model(&subscription_model)?; @@ -157,46 +119,55 @@ pub fn register_subscriptions_to_schema_builder( let task_id = task_service .add_subscriber_task( - auth_user_info.subscriber_auth.subscriber_id, + subscription_model.subscriber_id, SubscriberTask::SyncOneSubscriptionFeedsFull(subscription.into()), ) .await?; - Ok(Some(FieldValue::owned_any(SyncOneSubscriptionInfo { - task_id: task_id.to_string(), - }))) + let task_model = subscriber_tasks::Entity::find() + .filter(subscriber_tasks::Column::Id.eq(task_id.to_string())) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "SubscriberTask".into(), + })?; + + Ok(Some(FieldValue::owned_any(task_model))) }) - }, - ) - .argument(InputValue::new( - SyncOneSubscriptionFilterInput::arg_name(), - TypeRef::named_nn(SyncOneSubscriptionFilterInput::input_type_name()), - )), - ); + }), + ); - builder.mutations.push( - Field::new( - "subscriptionSyncOneSources", - TypeRef::named_nn(SyncOneSubscriptionInfo::object_type_name()), - move |ctx| { - FieldFuture::new(async move { - let auth_user_info = ctx.data::()?; - let app_ctx = ctx.data::>()?; + builder.mutations.push(sync_one_feeds_full_mutation); + } - let subscriber_id = auth_user_info.subscriber_auth.subscriber_id; + { + let sync_one_sources_mutation_name = format!( + "{}SyncOneSources", + entity_query_field.type_name::() + ); - let filter_input: SyncOneSubscriptionFilterInput = ctx - .args - .get(SyncOneSubscriptionFilterInput::arg_name()) - .unwrap() - .deserialize()?; + let sync_one_sources_mutation = generate_entity_filter_mutation_field::< + subscriptions::Entity, + _, + _, + >( + builder.context, + sync_one_sources_mutation_name, + TypeRef::named_nn(entity_object_builder.type_name::()), + Arc::new(|resolver_ctx, app_ctx, filters| { + let filters_condition = + get_filter_conditions::(resolver_ctx, context, filters); - let subscription_model = subscriptions::Model::find_by_id_and_subscriber_id( - app_ctx.as_ref(), - filter_input.id, - subscriber_id, - ) - .await?; + Box::pin(async move { + let db = app_ctx.db(); + + let subscription_model = subscriptions::Entity::find() + .filter(filters_condition) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "Subscription".into(), + })?; let subscription = subscriptions::Subscription::try_from_model(&subscription_model)?; @@ -205,22 +176,26 @@ pub fn register_subscriptions_to_schema_builder( let task_id = task_service .add_subscriber_task( - auth_user_info.subscriber_auth.subscriber_id, + subscription_model.subscriber_id, SubscriberTask::SyncOneSubscriptionSources(subscription.into()), ) .await?; - Ok(Some(FieldValue::owned_any(SyncOneSubscriptionInfo { - task_id: task_id.to_string(), - }))) + let task_model = subscriber_tasks::Entity::find() + .filter(subscriber_tasks::Column::Id.eq(task_id.to_string())) + .one(db) + .await? + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "SubscriberTask".into(), + })?; + + Ok(Some(FieldValue::owned_any(task_model))) }) - }, - ) - .argument(InputValue::new( - SyncOneSubscriptionFilterInput::arg_name(), - TypeRef::named_nn(SyncOneSubscriptionFilterInput::input_type_name()), - )), - ); + }), + ); + + builder.mutations.push(sync_one_sources_mutation); + } builder } diff --git a/apps/recorder/src/graphql/infra/custom.rs b/apps/recorder/src/graphql/infra/custom.rs index 403aff2..fab2678 100644 --- a/apps/recorder/src/graphql/infra/custom.rs +++ b/apps/recorder/src/graphql/infra/custom.rs @@ -1,33 +1,35 @@ use std::{pin::Pin, sync::Arc}; use async_graphql::dynamic::{ - Field, FieldFuture, InputValue, ResolverContext, TypeRef, ValueAccessor, + Field, FieldFuture, FieldValue, InputValue, ResolverContext, TypeRef, ValueAccessor, }; use sea_orm::EntityTrait; -use seaography::{ - BuilderContext, EntityDeleteMutationBuilder, EntityObjectBuilder, FilterInputBuilder, - GuardAction, -}; +use seaography::{BuilderContext, EntityObjectBuilder, FilterInputBuilder, GuardAction}; use crate::{app::AppContextTrait, errors::RecorderResult}; -pub type DeleteMutationFn = Arc< - dyn Fn( - &ResolverContext<'_>, +pub type FilterMutationFn = Arc< + dyn for<'a> Fn( + &ResolverContext<'a>, Arc, Option>, - ) -> Pin>> + Send>> - + Send + ) -> Pin< + Box>>> + Send + 'a>, + > + Send + Sync, >; -pub fn generate_custom_entity_delete_mutation_field( +pub fn generate_entity_filter_mutation_field( builder_context: &'static BuilderContext, - mutation_fn: DeleteMutationFn, + field_name: N, + type_ref: R, + mutation_fn: FilterMutationFn, ) -> Field where T: EntityTrait, ::Model: Sync, + N: Into, + R: Into, { let entity_filter_input_builder = FilterInputBuilder { context: builder_context, @@ -35,43 +37,38 @@ where let entity_object_builder = EntityObjectBuilder { context: builder_context, }; - let entity_delete_mutation_builder = EntityDeleteMutationBuilder { - context: builder_context, - }; let object_name: String = entity_object_builder.type_name::(); let context = builder_context; let guard = builder_context.guards.entity_guards.get(&object_name); - Field::new( - entity_delete_mutation_builder.type_name::(), - 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 - }; + Field::new(field_name, type_ref, move |ctx| { + let mutation_fn = mutation_fn.clone(); + FieldFuture::new(async move { + let guard_flag = if let Some(guard) = guard { + (*guard)(&ctx) + } else { + GuardAction::Allow + }; - if let GuardAction::Block(reason) = guard_flag { - return Err::, async_graphql::Error>(async_graphql::Error::new( - reason.unwrap_or("Entity guard triggered.".into()), - )); - } + 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 app_ctx = ctx.data::>()?; - let filters = ctx.args.get(&context.entity_delete_mutation.filter_field); + let filters = ctx.args.get(&context.entity_delete_mutation.filter_field); - let result = mutation_fn(&ctx, app_ctx.clone(), filters).await?; + let result = mutation_fn(&ctx, app_ctx.clone(), filters) + .await + .map_err(async_graphql::Error::new_with_source)?; - Ok(result.map(async_graphql::Value::from)) - }) - }, - ) + Ok(result) + }) + }) .argument(InputValue::new( &context.entity_delete_mutation.filter_field, TypeRef::named(entity_filter_input_builder.type_name(&object_name)), diff --git a/apps/recorder/src/models/auth.rs b/apps/recorder/src/models/auth.rs index 04d90da..992513b 100644 --- a/apps/recorder/src/models/auth.rs +++ b/apps/recorder/src/models/auth.rs @@ -99,7 +99,9 @@ impl Model { ..Default::default() }; - let new_item: Model = new_item.save(&txn).await?.try_into()?; + let new_item: Model = new_item.insert(&txn).await?; + + txn.commit().await?; Ok(new_item) } diff --git a/apps/recorder/src/models/subscriptions.rs b/apps/recorder/src/models/subscriptions.rs index f8382f2..a0ff808 100644 --- a/apps/recorder/src/models/subscriptions.rs +++ b/apps/recorder/src/models/subscriptions.rs @@ -186,19 +186,13 @@ impl Model { let subscription_model = Entity::find_by_id(subscription_id) .one(db) .await? - .ok_or_else(|| RecorderError::DbError { - source: DbErr::RecordNotFound(format!( - "Subscription id {subscription_id} not found or not belong to subscriber \ - {subscriber_id}", - )), + .ok_or_else(|| RecorderError::ModelEntityNotFound { + entity: "Subscription".into(), })?; if subscription_model.subscriber_id != subscriber_id { - Err(RecorderError::DbError { - source: DbErr::RecordNotFound(format!( - "Subscription id {subscription_id} not found or not belong to subscriber \ - {subscriber_id}", - )), + Err(RecorderError::ModelEntityNotFound { + entity: "Subscription".into(), })?; } diff --git a/apps/recorder/src/task/db.rs b/apps/recorder/src/task/db.rs deleted file mode 100644 index 2b097a4..0000000 --- a/apps/recorder/src/task/db.rs +++ /dev/null @@ -1,7 +0,0 @@ -use sea_orm::sea_query; - -#[derive(sea_query::Iden)] -pub enum ApalisJob { - Table, - Id, -} diff --git a/apps/recorder/src/task/extern.rs b/apps/recorder/src/task/extern.rs new file mode 100644 index 0000000..56edfc6 --- /dev/null +++ b/apps/recorder/src/task/extern.rs @@ -0,0 +1,16 @@ +use sea_orm::sea_query; + +#[derive(sea_query::Iden)] + +pub enum ApalisSchema { + #[iden = "apalis"] + Schema, +} + +#[derive(sea_query::Iden)] + +pub enum ApalisJobs { + #[iden = "jobs"] + Table, + Id, +} diff --git a/apps/recorder/src/task/mod.rs b/apps/recorder/src/task/mod.rs index 2a7b9d9..877b153 100644 --- a/apps/recorder/src/task/mod.rs +++ b/apps/recorder/src/task/mod.rs @@ -1,13 +1,13 @@ mod config; mod core; -mod db; +mod r#extern; mod registry; mod service; pub use core::{SUBSCRIBER_TASK_APALIS_NAME, SubscriberAsyncTaskTrait, SubscriberStreamTaskTrait}; pub use config::TaskConfig; -pub use db::ApalisJob; +pub use r#extern::{ApalisJobs, ApalisSchema}; pub use registry::{ SubscriberTask, SubscriberTaskType, SubscriberTaskTypeEnum, SubscriberTaskTypeVariant, SubscriberTaskTypeVariantIter, SyncOneSubscriptionFeedsFullTask, diff --git a/apps/recorder/src/task/service.rs b/apps/recorder/src/task/service.rs index a8234be..97fa4f8 100644 --- a/apps/recorder/src/task/service.rs +++ b/apps/recorder/src/task/service.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, sync::Arc}; +use std::{ops::Deref, str::FromStr, sync::Arc}; use apalis::prelude::*; use apalis_sql::{ @@ -10,7 +10,7 @@ use tokio::sync::RwLock; use crate::{ app::AppContextTrait, - errors::RecorderResult, + errors::{RecorderError, RecorderResult}, task::{SUBSCRIBER_TASK_APALIS_NAME, SubscriberTask, TaskConfig}, }; @@ -45,6 +45,19 @@ impl TaskService { job.run(ctx).await } + pub async fn retry_subscriber_task(&self, job_id: String) -> RecorderResult<()> { + { + let mut storage = self.subscriber_task_storage.write().await; + let task_id = + TaskId::from_str(&job_id).map_err(|err| RecorderError::InvalidTaskId { + message: err.to_string(), + })?; + let worker_id = WorkerId::new(SUBSCRIBER_TASK_APALIS_NAME); + storage.retry(&worker_id, &task_id).await?; + } + Ok(()) + } + pub async fn add_subscriber_task( &self, _subscriber_id: i32, @@ -65,15 +78,23 @@ impl TaskService { } pub async fn setup_monitor(&self) -> RecorderResult { - let monitor = Monitor::new(); - let worker = WorkerBuilder::new(SUBSCRIBER_TASK_APALIS_NAME) - .catch_panic() - .enable_tracing() - .data(self.ctx.clone()) - .backend(self.subscriber_task_storage.read().await.clone()) - .build_fn(Self::run_subscriber_task); + let mut monitor = Monitor::new(); - Ok(monitor.register(worker)) + { + let subscriber_task_worker = WorkerBuilder::new(SUBSCRIBER_TASK_APALIS_NAME) + .catch_panic() + .enable_tracing() + .data(self.ctx.clone()) + .backend({ + let storage = self.subscriber_task_storage.read().await; + storage.clone() + }) + .build_fn(Self::run_subscriber_task); + + monitor = monitor.register(subscriber_task_worker); + } + + Ok(monitor) } pub async fn setup_listener(&self) -> RecorderResult { diff --git a/apps/webui/src/domains/recorder/schema/subscriptions.ts b/apps/webui/src/domains/recorder/schema/subscriptions.ts index 92b7e3e..61bb0f7 100644 --- a/apps/webui/src/domains/recorder/schema/subscriptions.ts +++ b/apps/webui/src/domains/recorder/schema/subscriptions.ts @@ -123,25 +123,25 @@ query GetSubscriptionDetail ($id: Int!) { `; export const SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL = gql` - mutation SyncSubscriptionFeedsIncremental($id: Int!) { - subscriptionSyncOneFeedsIncremental(filter: { id: $id }) { - taskId + mutation SyncSubscriptionFeedsIncremental($filter: SubscriptionsFilterInput!) { + subscriptionsSyncOneFeedsIncremental(filter: $filter) { + id } } `; export const SYNC_SUBSCRIPTION_FEEDS_FULL = gql` - mutation SyncSubscriptionFeedsFull($id: Int!) { - subscriptionSyncOneFeedsFull(filter: { id: $id }) { - taskId + mutation SyncSubscriptionFeedsFull($filter: SubscriptionsFilterInput!) { + subscriptionsSyncOneFeedsFull(filter: $filter) { + id } } `; export const SYNC_SUBSCRIPTION_SOURCES = gql` - mutation SyncSubscriptionSources($id: Int!) { - subscriptionSyncOneSources(filter: { id: $id }) { - taskId + mutation SyncSubscriptionSources($filter: SubscriptionsFilterInput!) { + subscriptionsSyncOneSources(filter: $filter) { + id } } `; diff --git a/apps/webui/src/domains/recorder/schema/tasks.ts b/apps/webui/src/domains/recorder/schema/tasks.ts index 0ad54f9..6f721cb 100644 --- a/apps/webui/src/domains/recorder/schema/tasks.ts +++ b/apps/webui/src/domains/recorder/schema/tasks.ts @@ -35,6 +35,31 @@ export const GET_TASKS = gql` } `; +export const DELETE_TASKS = gql` + mutation DeleteTasks($filters: SubscriberTasksFilterInput!) { + subscriberTasksDelete(filter: $filters) + } +`; + +export const RETRY_TASKS = gql` + mutation RetryTasks($filters: SubscriberTasksFilterInput!) { + subscriberTasksRetryOne(filter: $filters) { + id, + job, + taskType, + status, + attempts, + maxAttempts, + runAt, + lastError, + lockAt, + lockBy, + doneAt, + priority + } + } +`; + export const TaskTypedSyncOneSubscriptionFeedsIncrementalSchema = type({ taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsIncremental}'`, }).and(SubscriptionSchema); diff --git a/apps/webui/src/infra/graphql/gql/gql.ts b/apps/webui/src/infra/graphql/gql/gql.ts index 8c4dad1..dc438e4 100644 --- a/apps/webui/src/infra/graphql/gql/gql.ts +++ b/apps/webui/src/infra/graphql/gql/gql.ts @@ -25,10 +25,12 @@ type Documents = { "\n mutation UpdateSubscriptions(\n $data: SubscriptionsUpdateInput!,\n $filters: SubscriptionsFilterInput!,\n ) {\n subscriptionsUpdate (\n data: $data\n filter: $filters\n ) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n }\n}\n": typeof types.UpdateSubscriptionsDocument, "\n mutation DeleteSubscriptions($filters: SubscriptionsFilterInput) {\n subscriptionsDelete(filter: $filters)\n }\n": typeof types.DeleteSubscriptionsDocument, "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filters: { id: {\n eq: $id\n } }) {\n nodes {\n id\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n rawName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n savePath\n homepage\n }\n }\n }\n }\n}\n": typeof types.GetSubscriptionDetailDocument, - "\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsIncrementalDocument, - "\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsFullDocument, - "\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionSourcesDocument, + "\n mutation SyncSubscriptionFeedsIncremental($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsIncremental(filter: $filter) {\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($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\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 DeleteTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksDelete(filter: $filters)\n }\n": typeof types.DeleteTasksDocument, + "\n mutation RetryTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksRetryOne(filter: $filters) {\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, }; const documents: Documents = { "\n query GetCredential3rd($filters: Credential3rdFilterInput!, $orderBy: Credential3rdOrderInput, $pagination: PaginationInput) {\n credential3rd(filters: $filters, orderBy: $orderBy, pagination: $pagination) {\n nodes {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": types.GetCredential3rdDocument, @@ -42,10 +44,12 @@ const documents: Documents = { "\n mutation UpdateSubscriptions(\n $data: SubscriptionsUpdateInput!,\n $filters: SubscriptionsFilterInput!,\n ) {\n subscriptionsUpdate (\n data: $data\n filter: $filters\n ) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n }\n}\n": types.UpdateSubscriptionsDocument, "\n mutation DeleteSubscriptions($filters: SubscriptionsFilterInput) {\n subscriptionsDelete(filter: $filters)\n }\n": types.DeleteSubscriptionsDocument, "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filters: { id: {\n eq: $id\n } }) {\n nodes {\n id\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n rawName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n savePath\n homepage\n }\n }\n }\n }\n}\n": types.GetSubscriptionDetailDocument, - "\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsIncrementalDocument, - "\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsFullDocument, - "\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionSourcesDocument, + "\n mutation SyncSubscriptionFeedsIncremental($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsIncremental(filter: $filter) {\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($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\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 DeleteTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksDelete(filter: $filters)\n }\n": types.DeleteTasksDocument, + "\n mutation RetryTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksRetryOne(filter: $filters) {\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, }; /** @@ -109,19 +113,27 @@ export function gql(source: "\nquery GetSubscriptionDetail ($id: Int!) {\n subs /** * 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($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n"]; +export function gql(source: "\n mutation SyncSubscriptionFeedsIncremental($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsIncremental(filter: $filter) {\n id\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsIncremental($filter: SubscriptionsFilterInput!) {\n subscriptionsSyncOneFeedsIncremental(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 SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n"]; +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($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n"]; +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($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\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($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\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 DeleteTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksDelete(filter: $filters)\n }\n"): (typeof documents)["\n mutation DeleteTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksDelete(filter: $filters)\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 RetryTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksRetryOne(filter: $filters) {\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 documents)["\n mutation RetryTasks($filters: SubscriberTasksFilterInput!) {\n subscriberTasksRetryOne(filter: $filters) {\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"]; export function gql(source: string) { return (documents as any)[source] ?? {}; diff --git a/apps/webui/src/infra/graphql/gql/graphql.ts b/apps/webui/src/infra/graphql/gql/graphql.ts index 3997a9f..2b1f837 100644 --- a/apps/webui/src/infra/graphql/gql/graphql.ts +++ b/apps/webui/src/infra/graphql/gql/graphql.ts @@ -114,7 +114,7 @@ export type BangumiFilterInput = { savePath?: InputMaybe; season?: InputMaybe; seasonRaw?: InputMaybe; - subscriberId?: InputMaybe; + subscriberId?: InputMaybe; updatedAt?: InputMaybe; }; @@ -133,7 +133,7 @@ export type BangumiInsertInput = { savePath?: InputMaybe; season: Scalars['Int']['input']; seasonRaw?: InputMaybe; - subscriberId?: InputMaybe; + subscriberId: Scalars['Int']['input']; updatedAt?: InputMaybe; }; @@ -171,6 +171,7 @@ export type BangumiUpdateInput = { savePath?: InputMaybe; season?: InputMaybe; seasonRaw?: InputMaybe; + subscriberId?: InputMaybe; updatedAt?: InputMaybe; }; @@ -813,10 +814,8 @@ export type Mutation = { episodesCreateOne: EpisodesBasic; episodesDelete: Scalars['Int']['output']; episodesUpdate: Array; - subscriberTasksCreateBatch: Array; - subscriberTasksCreateOne: SubscriberTasksBasic; subscriberTasksDelete: Scalars['Int']['output']; - subscriberTasksUpdate: Array; + subscriberTasksRetryOne: SubscriberTasks; subscriptionBangumiCreateBatch: Array; subscriptionBangumiCreateOne: SubscriptionBangumiBasic; subscriptionBangumiDelete: Scalars['Int']['output']; @@ -825,12 +824,12 @@ export type Mutation = { subscriptionEpisodeCreateOne: SubscriptionEpisodeBasic; subscriptionEpisodeDelete: Scalars['Int']['output']; subscriptionEpisodeUpdate: Array; - subscriptionSyncOneFeedsFull: SyncOneSubscriptionInfo; - subscriptionSyncOneFeedsIncremental: SyncOneSubscriptionInfo; - subscriptionSyncOneSources: SyncOneSubscriptionInfo; subscriptionsCreateBatch: Array; subscriptionsCreateOne: SubscriptionsBasic; subscriptionsDelete: Scalars['Int']['output']; + subscriptionsSyncOneFeedsFull: SubscriberTasks; + subscriptionsSyncOneFeedsIncremental: SubscriberTasks; + subscriptionsSyncOneSources: SubscriberTasks; subscriptionsUpdate: Array; }; @@ -940,23 +939,12 @@ export type MutationEpisodesUpdateArgs = { }; -export type MutationSubscriberTasksCreateBatchArgs = { - data: Array; -}; - - -export type MutationSubscriberTasksCreateOneArgs = { - data: SubscriberTasksInsertInput; -}; - - export type MutationSubscriberTasksDeleteArgs = { filter?: InputMaybe; }; -export type MutationSubscriberTasksUpdateArgs = { - data: SubscriberTasksUpdateInput; +export type MutationSubscriberTasksRetryOneArgs = { filter?: InputMaybe; }; @@ -1003,21 +991,6 @@ export type MutationSubscriptionEpisodeUpdateArgs = { }; -export type MutationSubscriptionSyncOneFeedsFullArgs = { - filter: SyncOneSubscriptionFilterInput; -}; - - -export type MutationSubscriptionSyncOneFeedsIncrementalArgs = { - filter: SyncOneSubscriptionFilterInput; -}; - - -export type MutationSubscriptionSyncOneSourcesArgs = { - filter: SyncOneSubscriptionFilterInput; -}; - - export type MutationSubscriptionsCreateBatchArgs = { data: Array; }; @@ -1033,6 +1006,21 @@ export type MutationSubscriptionsDeleteArgs = { }; +export type MutationSubscriptionsSyncOneFeedsFullArgs = { + filter?: InputMaybe; +}; + + +export type MutationSubscriptionsSyncOneFeedsIncrementalArgs = { + filter?: InputMaybe; +}; + + +export type MutationSubscriptionsSyncOneSourcesArgs = { + filter?: InputMaybe; +}; + + export type MutationSubscriptionsUpdateArgs = { data: SubscriptionsUpdateInput; filter?: InputMaybe; @@ -1231,23 +1219,6 @@ export type SubscriberTasks = { taskType: SubscriberTaskTypeEnum; }; -export type SubscriberTasksBasic = { - __typename?: 'SubscriberTasksBasic'; - attempts: Scalars['Int']['output']; - doneAt?: Maybe; - id: Scalars['String']['output']; - job: Scalars['Json']['output']; - lastError?: Maybe; - lockAt?: Maybe; - lockBy?: Maybe; - maxAttempts: Scalars['Int']['output']; - priority: Scalars['Int']['output']; - runAt: Scalars['String']['output']; - status: SubscriberTaskStatusEnum; - subscriberId: Scalars['Int']['output']; - taskType: SubscriberTaskTypeEnum; -}; - export type SubscriberTasksConnection = { __typename?: 'SubscriberTasksConnection'; edges: Array; @@ -1280,22 +1251,6 @@ export type SubscriberTasksFilterInput = { taskType?: InputMaybe; }; -export type SubscriberTasksInsertInput = { - attempts: Scalars['Int']['input']; - doneAt?: InputMaybe; - id?: InputMaybe; - job: Scalars['Json']['input']; - lastError?: InputMaybe; - lockAt?: InputMaybe; - lockBy?: InputMaybe; - maxAttempts: Scalars['Int']['input']; - priority: Scalars['Int']['input']; - runAt: Scalars['String']['input']; - status: SubscriberTaskStatusEnum; - subscriberId?: InputMaybe; - taskType: SubscriberTaskTypeEnum; -}; - export type SubscriberTasksOrderInput = { attempts?: InputMaybe; doneAt?: InputMaybe; @@ -1312,21 +1267,6 @@ export type SubscriberTasksOrderInput = { taskType?: InputMaybe; }; -export type SubscriberTasksUpdateInput = { - attempts?: InputMaybe; - doneAt?: InputMaybe; - id?: InputMaybe; - job?: InputMaybe; - lastError?: InputMaybe; - lockAt?: InputMaybe; - lockBy?: InputMaybe; - maxAttempts?: InputMaybe; - priority?: InputMaybe; - runAt?: InputMaybe; - status?: InputMaybe; - taskType?: InputMaybe; -}; - export type Subscribers = { __typename?: 'Subscribers'; bangumi: BangumiConnection; @@ -1672,17 +1612,6 @@ export type SubscriptionsUpdateInput = { updatedAt?: InputMaybe; }; -/** The input of the subscriptionSyncOne series of mutations */ -export type SyncOneSubscriptionFilterInput = { - id: Scalars['Int']['input']; -}; - -/** The output of the subscriptionSyncOne series of mutations */ -export type SyncOneSubscriptionInfo = { - __typename?: 'SyncOneSubscriptionInfo'; - taskId: Scalars['String']['output']; -}; - export type TextFilterInput = { between?: InputMaybe>; eq?: InputMaybe; @@ -1782,25 +1711,25 @@ export type GetSubscriptionDetailQueryVariables = Exact<{ export type GetSubscriptionDetailQuery = { __typename?: 'Query', subscriptions: { __typename?: 'SubscriptionsConnection', nodes: Array<{ __typename?: 'Subscriptions', id: number, displayName: string, createdAt: string, updatedAt: string, category: SubscriptionCategoryEnum, sourceUrl: string, enabled: boolean, 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, rawName: string, season: number, seasonRaw?: string | null, fansub?: string | null, mikanFansubId?: string | null, rssLink?: string | null, posterLink?: string | null, savePath?: string | null, homepage?: string | null }> } }> } }; export type SyncSubscriptionFeedsIncrementalMutationVariables = Exact<{ - id: Scalars['Int']['input']; + filter: SubscriptionsFilterInput; }>; -export type SyncSubscriptionFeedsIncrementalMutation = { __typename?: 'Mutation', subscriptionSyncOneFeedsIncremental: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } }; +export type SyncSubscriptionFeedsIncrementalMutation = { __typename?: 'Mutation', subscriptionsSyncOneFeedsIncremental: { __typename?: 'SubscriberTasks', id: string } }; export type SyncSubscriptionFeedsFullMutationVariables = Exact<{ - id: Scalars['Int']['input']; + filter: SubscriptionsFilterInput; }>; -export type SyncSubscriptionFeedsFullMutation = { __typename?: 'Mutation', subscriptionSyncOneFeedsFull: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } }; +export type SyncSubscriptionFeedsFullMutation = { __typename?: 'Mutation', subscriptionsSyncOneFeedsFull: { __typename?: 'SubscriberTasks', id: string } }; export type SyncSubscriptionSourcesMutationVariables = Exact<{ - id: Scalars['Int']['input']; + filter: SubscriptionsFilterInput; }>; -export type SyncSubscriptionSourcesMutation = { __typename?: 'Mutation', subscriptionSyncOneSources: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } }; +export type SyncSubscriptionSourcesMutation = { __typename?: 'Mutation', subscriptionsSyncOneSources: { __typename?: 'SubscriberTasks', id: string } }; export type GetTasksQueryVariables = Exact<{ filters: SubscriberTasksFilterInput; @@ -1811,6 +1740,20 @@ 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 DeleteTasksMutationVariables = Exact<{ + filters: SubscriberTasksFilterInput; +}>; + + +export type DeleteTasksMutation = { __typename?: 'Mutation', subscriberTasksDelete: number }; + +export type RetryTasksMutationVariables = Exact<{ + filters: SubscriberTasksFilterInput; +}>; + + +export type RetryTasksMutation = { __typename?: 'Mutation', subscriberTasksRetryOne: { __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 } }; + export const GetCredential3rdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCredential3rd"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdFilterInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdOrderInput"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential3rd"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}}],"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":"cookies"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"credentialType"}}]}},{"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 InsertCredential3rdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InsertCredential3rd"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential3rdCreateOne"},"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"}},{"kind":"Field","name":{"kind":"Name","value":"cookies"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"credentialType"}}]}}]}}]} as unknown as DocumentNode; @@ -1823,7 +1766,9 @@ 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":"filters"}},"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":"filters"}}}],"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":"filters"}},"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":"filters"}}}]}]}}]} 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":"filters"},"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":"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":"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":"rawName"}},{"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":"savePath"}},{"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":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionSyncOneFeedsIncremental"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"taskId"}}]}}]}}]} 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":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionSyncOneFeedsFull"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"taskId"}}]}}]}}]} 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":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionSyncOneSources"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"taskId"}}]}}]}}]} 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":"filters"}},"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":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"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; \ No newline at end of file +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":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsSyncOneFeedsIncremental"},"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 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":"filters"}},"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":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"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 DeleteTasksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteTasks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"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":"filters"}}}]}]}}]} 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":"filters"}},"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":"filters"}}}],"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 4583780..c404edc 100644 --- a/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx +++ b/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx @@ -26,7 +26,7 @@ import { memo, useCallback } from 'react'; import { toast } from 'sonner'; export type SubscriptionSyncViewCompletePayload = { - taskId: string; + id: string; }; export interface SubscriptionSyncViewProps { @@ -43,7 +43,7 @@ export const SubscriptionSyncView = memo( >(SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL, { onCompleted: (data) => { toast.success('Sync completed'); - onComplete(data.subscriptionSyncOneFeedsIncremental); + onComplete(data.subscriptionsSyncOneFeedsIncremental); }, onError: (error) => { toast.error('Failed to sync subscription', { @@ -58,7 +58,7 @@ export const SubscriptionSyncView = memo( >(SYNC_SUBSCRIPTION_FEEDS_FULL, { onCompleted: (data) => { toast.success('Sync completed'); - onComplete(data.subscriptionSyncOneFeedsFull); + onComplete(data.subscriptionsSyncOneFeedsFull); }, onError: (error) => { toast.error('Failed to sync subscription', { @@ -73,7 +73,7 @@ export const SubscriptionSyncView = memo( >(SYNC_SUBSCRIPTION_SOURCES, { onCompleted: (data) => { toast.success('Sync completed'); - onComplete(data.subscriptionSyncOneSources); + onComplete(data.subscriptionsSyncOneSources); }, onError: (error) => { toast.error('Failed to sync subscription', { @@ -89,7 +89,11 @@ export const SubscriptionSyncView = memo( + )} diff --git a/apps/webui/src/presentation/routes/_app/tasks/manage.tsx b/apps/webui/src/presentation/routes/_app/tasks/manage.tsx index 20a1c6d..9dc719e 100644 --- a/apps/webui/src/presentation/routes/_app/tasks/manage.tsx +++ b/apps/webui/src/presentation/routes/_app/tasks/manage.tsx @@ -5,14 +5,23 @@ import { DetailEmptyView } from '@/components/ui/detail-empty-view'; import { DropdownMenuActions } from '@/components/ui/dropdown-menu-actions'; import { QueryErrorView } from '@/components/ui/query-error-view'; import { Skeleton } from '@/components/ui/skeleton'; -import { GET_TASKS, type TaskDto } from '@/domains/recorder/schema/tasks'; import { + DELETE_TASKS, + GET_TASKS, + RETRY_TASKS, + type TaskDto, +} from '@/domains/recorder/schema/tasks'; +import { + type DeleteTasksMutation, + type DeleteTasksMutationVariables, type GetTasksQuery, + type RetryTasksMutation, + type RetryTasksMutationVariables, SubscriberTaskStatusEnum, } from '@/infra/graphql/gql/graphql'; import type { RouteStateDataOption } from '@/infra/routes/traits'; import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton'; -import { useQuery } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { type ColumnDef, @@ -26,7 +35,13 @@ import { import { format } from 'date-fns'; import { RefreshCw } from 'lucide-react'; +import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; import { useMemo, useState } from 'react'; +import { toast } from 'sonner'; import { getStatusBadge } from './-status-badge'; export const Route = createFileRoute('/_app/tasks/manage')({ @@ -70,6 +85,42 @@ function TaskManageRouteComponent() { const tasks = data?.subscriberTasks; + const [deleteTasks] = useMutation< + DeleteTasksMutation, + DeleteTasksMutationVariables + >(DELETE_TASKS, { + onCompleted: async () => { + const refetchResult = await refetch(); + const error = getApolloQueryError(refetchResult); + if (error) { + toast.error('Failed to delete tasks', { + description: apolloErrorToMessage(error), + }); + return; + } + toast.success('Tasks deleted'); + }, + onError: (error) => { + toast.error('Failed to delete tasks', { + description: error.message, + }); + }, + }); + + const [retryTasks] = useMutation< + RetryTasksMutation, + RetryTasksMutationVariables + >(RETRY_TASKS, { + onCompleted: () => { + toast.success('Tasks retried'); + }, + onError: (error) => { + toast.error('Failed to retry tasks', { + description: error.message, + }); + }, + }); + const columns = useMemo(() => { const cs: ColumnDef[] = [ { @@ -167,7 +218,39 @@ function TaskManageRouteComponent() { params: { id: task.id }, }); }} - /> + showDelete + onDelete={() => + deleteTasks({ + variables: { + filters: { + id: { + eq: task.id, + }, + }, + }, + }) + } + > + {task.status === + (SubscriberTaskStatusEnum.Killed || + SubscriberTaskStatusEnum.Failed) && ( + + retryTasks({ + variables: { + filters: { + id: { + eq: task.id, + }, + }, + }, + }) + } + > + Retry + + )} +