Compare commits

...

3 Commits

Author SHA1 Message Date
5b001f9584 refactor: refactor graphql 2025-07-02 01:25:44 +08:00
d06acde882 fix: temp save 2025-07-01 03:45:56 +08:00
bacfe99ef2 fix: fix issues 2025-06-30 02:05:23 +08:00
45 changed files with 586 additions and 1210 deletions

12
.vscode/settings.json vendored
View File

@ -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",
}
}

20
Cargo.lock generated
View File

@ -6793,6 +6793,7 @@ dependencies = [
"once_cell",
"opendal",
"openidconnect",
"paste",
"percent-encoding",
"polars",
"quick-xml",
@ -6807,6 +6808,7 @@ dependencies = [
"sea-orm",
"sea-orm-migration",
"seaography",
"secrecy",
"serde",
"serde_json",
"serde_variant",
@ -7643,16 +7645,16 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "seaography"
version = "1.1.4"
source = "git+https://github.com/dumtruck/seaography.git?rev=a787c3a#a787c3ab83cf1f8275894e1bc1ca3c766b54674b"
source = "git+https://github.com/dumtruck/seaography.git?rev=9f7fc7c#9f7fc7cf05234abe35fd9144c895321dd2b5db62"
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]]
@ -7669,6 +7671,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "secrecy"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"serde",
"zeroize",
]
[[package]]
name = "security-framework"
version = "2.11.1"

View File

@ -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,4 @@ 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 = "9f7fc7c" }

View File

@ -125,6 +125,7 @@ seaography = { version = "1.1", features = [
"with-bigdecimal",
"with-postgres-array",
"with-json-as-scalar",
"with-custom-as-json",
] }
tower = { version = "0.5.2", features = ["util"] }
tower-http = { version = "0.6", features = [
@ -166,6 +167,8 @@ quick-xml = { version = "0.37.5", features = [
] }
croner = "2.2.0"
ts-rs = "11.0.1"
secrecy = { version = "0.10.3", features = ["serde"] }
paste = "1.0.15"
[dev-dependencies]
inquire = { workspace = true }

View File

@ -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 { SyncOneSubscriptionFeedsFullTaskInput } from "./SyncOneSubscriptionFeedsFullTaskInput";
import type { SyncOneSubscriptionFeedsIncrementalTaskInput } from "./SyncOneSubscriptionFeedsIncrementalTaskInput";
import type { SyncOneSubscriptionSourcesTaskInput } from "./SyncOneSubscriptionSourcesTaskInput";
export type SubscriberTaskInput = { "taskType": "sync_one_subscription_feeds_incremental" } & SyncOneSubscriptionFeedsIncrementalTaskInput | { "taskType": "sync_one_subscription_feeds_full" } & SyncOneSubscriptionFeedsFullTaskInput | { "taskType": "sync_one_subscription_sources" } & SyncOneSubscriptionSourcesTaskInput;

View File

@ -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 SubscriberTaskType = { "taskType": "sync_one_subscription_feeds_incremental" } & SyncOneSubscriptionFeedsIncrementalTask | { "taskType": "sync_one_subscription_feeds_full" } & SyncOneSubscriptionFeedsFullTask | { "taskType": "sync_one_subscription_sources" } & SyncOneSubscriptionSourcesTask;

View File

@ -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, };

View File

@ -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 SyncOneSubscriptionFeedsFullTaskInput = { subscriptionId: number, subscriberId?: number | null, cronId?: number | null, };

View File

@ -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, };

View File

@ -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 SyncOneSubscriptionFeedsIncrementalTaskInput = { subscriptionId: number, subscriberId?: number | null, cronId?: number | null, };

View File

@ -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, };

View File

@ -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 SyncOneSubscriptionSourcesTaskInput = { subscriptionId: number, subscriberId?: number | null, cronId?: number | null, };

View File

@ -0,0 +1,6 @@
{
"name": "recorder",
"version": "0.0.1",
"private": true,
"type": "module"
}

View File

@ -313,4 +313,10 @@ impl From<http::method::InvalidMethod> for RecorderError {
}
}
impl From<async_graphql::Error> for RecorderError {
fn from(error: async_graphql::Error) -> Self {
seaography::SeaographyError::AsyncGraphQLError(error).into()
}
}
pub type RecorderResult<T> = Result<T, RecorderError>;

View File

@ -101,7 +101,7 @@ 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,

View File

@ -1,20 +1,15 @@
use convert_case::Case;
use sea_orm::Iterable;
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_case_for_entity, restrict_jsonb_filter_input_for_entity,
validate_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 +17,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 +43,7 @@ fn skip_columns_for_entity_input(context: &mut BuilderContext) {
pub fn register_cron_to_schema_context(context: &mut BuilderContext) {
restrict_subscriber_for_entity::<cron::Entity>(context, &cron::Column::SubscriberId);
restrict_jsonb_filter_input_for_entity::<cron::Entity>(context, &cron::Column::SubscriberTask);
convert_jsonb_output_case_for_entity::<cron::Entity>(
context,
&cron::Column::SubscriberTask,
Case::Camel,
);
validate_jsonb_input_for_entity::<cron::Entity, Option<subscriber_tasks::SubscriberTask>>(
context,
&cron::Column::SubscriberTask,
);
restrict_subscriber_tasks_for_entity::<cron::Entity>(context, &cron::Column::SubscriberTask);
skip_columns_for_entity_input(context);
}

View File

@ -1,12 +1,15 @@
use std::{ops::Deref, sync::Arc};
use async_graphql::dynamic::{FieldValue, TypeRef, ValueAccessor};
use async_graphql::dynamic::{FieldValue, Scalar, 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, GuardAction};
use seaography::{
Builder as SeaographyBuilder, BuilderContext, SeaographyError, prepare_active_model,
};
use ts_rs::TS;
use crate::{
auth::AuthUserInfo,
@ -20,59 +23,22 @@ use crate::{
generate_entity_default_insert_input_object, generate_entity_delete_mutation_field,
generate_entity_filtered_mutation_field, register_entity_default_readonly,
},
json::{
convert_jsonb_output_case_for_entity, restrict_jsonb_filter_input_for_entity,
validate_jsonb_input_for_entity,
},
json::{convert_jsonb_output_for_entity, restrict_jsonb_filter_input_for_entity},
name::{
get_column_name, get_entity_and_column_name, 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_custom_mutation_field_name,
get_entity_update_mutation_field_name,
get_entity_and_column_name, get_entity_basic_type_name,
get_entity_custom_mutation_field_name,
},
},
},
models::subscriber_tasks,
task::{ApalisJobs, ApalisSchema},
task::{ApalisJobs, ApalisSchema, SubscriberTaskTrait},
};
pub fn check_entity_and_task_subscriber_id_matches(
value_accessor: &ValueAccessor<'_>,
subscriber_id: i32,
subscriber_id_column_name: &str,
subscriber_task_column_name: &str,
) -> bool {
value_accessor.object().is_ok_and(|input_object| {
input_object
.get(subscriber_task_column_name)
.and_then(|subscriber_task_value| subscriber_task_value.object().ok())
.and_then(|subscriber_task_object| {
subscriber_task_object
.get("subscriber_id")
.and_then(|job_subscriber_id| job_subscriber_id.i64().ok())
})
.is_some_and(|subscriber_task_subscriber_id| {
subscriber_task_subscriber_id as i32
== input_object
.get(subscriber_id_column_name)
.and_then(|subscriber_id_object| subscriber_id_object.i64().ok())
.map(|subscriber_id| subscriber_id as i32)
.unwrap_or(subscriber_id)
})
})
}
fn skip_columns_for_entity_input(context: &mut BuilderContext) {
for column in subscriber_tasks::Column::iter() {
if matches!(
column,
subscriber_tasks::Column::Job
| subscriber_tasks::Column::Id
| subscriber_tasks::Column::SubscriberId
| subscriber_tasks::Column::Priority
| subscriber_tasks::Column::MaxAttempts
subscriber_tasks::Column::Job | subscriber_tasks::Column::SubscriberId
) {
continue;
}
@ -82,115 +48,77 @@ fn skip_columns_for_entity_input(context: &mut BuilderContext) {
}
}
pub fn restrict_subscriber_tasks_for_entity<T>(context: &mut BuilderContext, column: &T::Column)
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_and_column = get_entity_and_column_name::<T>(context, column);
restrict_jsonb_filter_input_for_entity::<T>(context, column);
convert_jsonb_output_for_entity::<T>(context, column, Some(Case::Camel));
let entity_column_name = get_entity_and_column_name::<T>(context, column);
context.types.input_type_overwrites.insert(
entity_column_name.clone(),
TypeRef::Named(subscriber_tasks::SubscriberTask::ident().into()),
);
context.types.output_type_overwrites.insert(
entity_column_name.clone(),
TypeRef::Named(subscriber_tasks::SubscriberTask::ident().into()),
);
context.types.input_conversions.insert(
entity_column_name.clone(),
Box::new(move |resolve_context, value_accessor| {
let task: subscriber_tasks::SubscriberTaskInput = value_accessor.deserialize()?;
let subscriber_id = resolve_context
.data::<AuthUserInfo>()?
.subscriber_auth
.subscriber_id;
let task = subscriber_tasks::SubscriberTask::from_input(task, subscriber_id);
let json_value = serde_json::to_value(task).map_err(|err| {
SeaographyError::TypeConversionError(
err.to_string(),
format!("Json - {entity_column_name}"),
)
})?;
Ok(sea_orm::Value::Json(Some(Box::new(json_value))))
}),
);
context.entity_input.update_skips.push(entity_and_column);
}
pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext) {
restrict_subscriber_for_entity::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::SubscriberId,
);
restrict_jsonb_filter_input_for_entity::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::Job,
);
convert_jsonb_output_case_for_entity::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::Job,
Case::Camel,
);
validate_jsonb_input_for_entity::<subscriber_tasks::Entity, subscriber_tasks::SubscriberTask>(
restrict_subscriber_tasks_for_entity::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::Job,
);
skip_columns_for_entity_input(context);
context.guards.field_guards.insert(
get_entity_and_column_name::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::Job,
),
{
let create_one_mutation_field_name =
Arc::new(get_entity_create_one_mutation_field_name::<
subscriber_tasks::Entity,
>(context));
let create_one_mutation_data_field_name =
Arc::new(get_entity_create_one_mutation_data_field_name(context).to_string());
let create_batch_mutation_field_name =
Arc::new(get_entity_create_batch_mutation_field_name::<
subscriber_tasks::Entity,
>(context));
let create_batch_mutation_data_field_name =
Arc::new(get_entity_create_batch_mutation_data_field_name(context).to_string());
let update_mutation_field_name = Arc::new(get_entity_update_mutation_field_name::<
subscriber_tasks::Entity,
>(context));
let job_column_name = Arc::new(get_column_name::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::Job,
));
let subscriber_id_column_name = Arc::new(get_column_name::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::SubscriberId,
));
Box::new(move |resolve_context| {
let field_name = resolve_context.field().name();
let subscriber_id = resolve_context
.data_opt::<AuthUserInfo>()
.unwrap()
.subscriber_auth
.subscriber_id;
let matched_subscriber_id = match field_name {
field if field == create_one_mutation_field_name.as_str() => resolve_context
.args
.get(create_one_mutation_data_field_name.as_str())
.is_some_and(|value_accessor| {
check_entity_and_task_subscriber_id_matches(
&value_accessor,
subscriber_id,
subscriber_id_column_name.as_str(),
job_column_name.as_str(),
)
}),
field if field == create_batch_mutation_field_name.as_str() => resolve_context
.args
.get(create_batch_mutation_data_field_name.as_str())
.and_then(|value| value.list().ok())
.is_some_and(|list| {
list.iter().all(|value| {
check_entity_and_task_subscriber_id_matches(
&value,
subscriber_id,
subscriber_id_column_name.as_str(),
job_column_name.as_str(),
)
})
}),
field if field == update_mutation_field_name.as_str() => {
unreachable!("subscriberTask entity do not support update job")
}
_ => true,
};
if matched_subscriber_id {
GuardAction::Allow
} else {
GuardAction::Block(Some(
"subscriber_id mismatch between entity and job".to_string(),
))
}
})
},
);
}
pub fn register_subscriber_tasks_to_schema_builder(
mut builder: SeaographyBuilder,
) -> SeaographyBuilder {
builder.schema = builder.schema.register(
Scalar::new(subscriber_tasks::SubscriberTask::ident())
.description(subscriber_tasks::SubscriberTask::decl()),
);
builder.register_enumeration::<subscriber_tasks::SubscriberTaskType>();
builder.register_enumeration::<subscriber_tasks::SubscriberTaskStatus>();
builder = register_entity_default_readonly!(builder, subscriber_tasks);
let builder_context = builder.context;
{
builder
.outputs
@ -278,23 +206,30 @@ pub fn register_subscriber_tasks_to_schema_builder(
subscriber_tasks::Entity,
>(builder_context));
let create_one_mutation =
generate_entity_create_one_mutation_field::<subscriber_tasks::Entity, TypeRef>(
generate_entity_create_one_mutation_field::<subscriber_tasks::Entity>(
builder_context,
None,
Arc::new(|_resolver_ctx, app_ctx, input_object| {
let job_column_name = get_column_name::<subscriber_tasks::Entity>(
builder_context,
&subscriber_tasks::Column::Job,
);
let task = input_object
.get(job_column_name.as_str())
.unwrap()
.deserialize::<subscriber_tasks::SubscriberTask>()
.unwrap();
Arc::new(move |resolver_ctx, app_ctx, input_object| {
Box::pin(async move {
let active_model: Result<subscriber_tasks::ActiveModel, _> =
prepare_active_model(builder_context, &input_object, resolver_ctx);
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();
if task.get_subscriber_id() != subscriber_id {
Err(async_graphql::Error::new(
"subscriber_id does not match with job.subscriber_id",
))?;
}
let task_id = task_service.add_subscriber_task(task).await?.to_string();
let db = app_ctx.db();

View File

@ -7,7 +7,7 @@ use sea_orm::{ColumnTrait, Condition, EntityTrait, Iterable, Value as SeaValue};
use seaography::{
Builder as SeaographyBuilder, BuilderContext, FilterInfo,
FilterOperation as SeaographqlFilterOperation, FilterType, FilterTypesMapHelper,
FnFilterCondition, FnGuard, FnInputTypeNoneConversion, GuardAction, SeaResult, SeaographyError,
FnFilterCondition, FnGuard, FnInputTypeNoneConversion, GuardAction, SeaResult,
};
use crate::{
@ -219,11 +219,10 @@ where
if let Some(value) = filter.get("eq") {
let value: i32 = value.i64()?.try_into()?;
if value != subscriber_id {
return Err(SeaographyError::AsyncGraphQLError(
async_graphql::Error::new(
"subscriber_id and auth_info does not match",
),
));
return Err(async_graphql::Error::new(
"subscriber_id and auth_info does not match",
)
.into());
}
}
}
@ -319,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);

View File

@ -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,
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::<subscriptions::SubscriptionCategory>();
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::<subscriptions::Entity, _, _>(
builder.context,
sync_one_feeds_incremental_mutation_name,
TypeRef::named_nn(get_entity_basic_type_name::<subscriber_tasks::Entity>(
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::<subscriptions::Entity>()
})?;
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::<subscriber_tasks::Entity>()
})?;
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::<subscriptions::Entity, _, _>(
builder.context,
sync_one_feeds_full_mutation_name,
TypeRef::named_nn(get_entity_basic_type_name::<subscriber_tasks::Entity>(
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::<subscriptions::Entity>()
})?;
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::<subscriber_tasks::Entity>()
})?;
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::<subscriptions::Entity, _, _>(
builder.context,
sync_one_sources_mutation_name,
TypeRef::named_nn(get_entity_basic_type_name::<subscriber_tasks::Entity>(
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::<subscriptions::Entity>()
})?;
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::<subscriber_tasks::Entity>()
})?;
Ok(Some(FieldValue::owned_any(task_model)))
})
}),
);
builder.mutations.push(sync_one_sources_mutation);
}
builder
}

View File

@ -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, SeaographyError,
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,
},
};
@ -41,9 +34,9 @@ pub type FilterMutationFn = Arc<
pub type CreateOneMutationFn<M> = Arc<
dyn for<'a> Fn(
&ResolverContext<'a>,
&'a ResolverContext<'a>,
Arc<dyn AppContextTrait>,
ObjectAccessor<'_>,
ObjectAccessor<'a>,
) -> Pin<Box<dyn Future<Output = RecorderResult<M>> + Send + 'a>>
+ Send
+ Sync,
@ -51,9 +44,9 @@ pub type CreateOneMutationFn<M> = Arc<
pub type CreateBatchMutationFn<M> = Arc<
dyn for<'a> Fn(
&ResolverContext<'a>,
&'a ResolverContext<'a>,
Arc<dyn AppContextTrait>,
Vec<ObjectAccessor<'_>>,
Vec<ObjectAccessor<'a>>,
) -> Pin<Box<dyn Future<Output = RecorderResult<Vec<M>>> + Send + 'a>>
+ Send
+ Sync,
@ -61,10 +54,10 @@ pub type CreateBatchMutationFn<M> = Arc<
pub type UpdateMutationFn<M> = Arc<
dyn for<'a> Fn(
&ResolverContext<'a>,
&'a ResolverContext<'a>,
Arc<dyn AppContextTrait>,
Condition,
ObjectAccessor<'_>,
ObjectAccessor<'a>,
) -> Pin<Box<dyn Future<Output = RecorderResult<Vec<M>>> + Send + 'a>>
+ Send
+ Sync,
@ -80,48 +73,46 @@ pub type DeleteMutationFn = Arc<
+ Sync,
>;
pub fn generate_entity_default_insert_input_object<T>(
builder_context: &'static BuilderContext,
) -> InputObject
pub fn generate_entity_default_insert_input_object<T>(context: &BuilderContext) -> InputObject
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_input_builder = seaography::EntityInputBuilder {
context: builder_context,
};
entity_input_builder.insert_input_object::<T>()
EntityInputBuilder::insert_input_object::<T>(context)
}
pub fn generate_entity_default_update_input_object<T>(
builder_context: &'static BuilderContext,
) -> InputObject
pub fn generate_entity_default_update_input_object<T>(context: &BuilderContext) -> InputObject
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_input_builder = seaography::EntityInputBuilder {
context: builder_context,
};
entity_input_builder.update_input_object::<T>()
EntityInputBuilder::update_input_object::<T>(context)
}
pub fn generate_entity_default_basic_entity_object<T>(
builder_context: &'static BuilderContext,
) -> Object
pub fn generate_entity_default_basic_entity_object<T>(context: &'static BuilderContext) -> Object
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_object_builder = seaography::EntityObjectBuilder {
context: builder_context,
};
let entity_object_builder = EntityObjectBuilder { context };
entity_object_builder.basic_to_object::<T>()
}
pub fn generate_entity_input_object<T>(
context: &'static BuilderContext,
is_insert: bool,
) -> InputObject
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
if is_insert {
EntityInputBuilder::insert_input_object::<T>(context)
} else {
EntityInputBuilder::update_input_object::<T>(context)
}
}
pub fn generate_entity_filtered_mutation_field<E, N, R>(
builder_context: &'static BuilderContext,
field_name: N,
@ -138,11 +129,12 @@ where
let guard = builder_context.guards.entity_guards.get(&object_name);
Field::new(field_name, type_ref, move |ctx| {
Field::new(field_name, type_ref, move |resolve_context| {
let mutation_fn = mutation_fn.clone();
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
(*guard)(&resolve_context)
} else {
GuardAction::Allow
};
@ -153,15 +145,15 @@ where
));
}
let app_ctx = ctx.data::<Arc<dyn AppContextTrait>>()?;
let filters = resolve_context
.args
.get(get_entity_renormalized_filter_field_name());
let filters = ctx.args.get(get_entity_renormalized_filter_field_name());
let filters = get_filter_conditions::<E>(&resolve_context, builder_context, filters);
let filters = get_filter_conditions::<E>(&ctx, builder_context, filters);
let app_ctx = resolve_context.data::<Arc<dyn AppContextTrait>>()?;
let result = mutation_fn(&ctx, app_ctx.clone(), filters)
.await
.map_err(async_graphql::Error::new_with_source)?;
let result = mutation_fn(&resolve_context, app_ctx.clone(), filters).await?;
Ok(result)
})
@ -172,314 +164,71 @@ where
))
}
pub fn generate_entity_create_one_mutation_field<E, ID>(
pub fn generate_entity_create_one_mutation_field<E>(
builder_context: &'static BuilderContext,
input_data_type_ref: Option<ID>,
mutation_fn: CreateOneMutationFn<E::Model>,
) -> Field
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
ID: Into<TypeRef>,
{
let guard = builder_context
.guards
.entity_guards
.get(&get_entity_name::<E>(builder_context));
let field_guards = &builder_context.guards.field_guards;
Field::new(
get_entity_create_one_mutation_field_name::<E>(builder_context),
TypeRef::named_nn(get_entity_basic_type_name::<E>(builder_context)),
move |ctx| {
let entity_create_one_mutation_builder = EntityCreateOneMutationBuilder {
context: builder_context,
};
entity_create_one_mutation_builder.to_field_with_mutation_fn::<E>(Arc::new(
move |resolver_ctx, input_object| {
let mutation_fn = mutation_fn.clone();
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
} else {
GuardAction::Allow
};
if let GuardAction::Block(reason) = guard_flag {
return Err::<Option<_>, async_graphql::Error>(async_graphql::Error::new(
reason.unwrap_or("Entity guard triggered.".into()),
));
}
Box::pin(async move {
let app_ctx = resolver_ctx.data::<Arc<dyn AppContextTrait>>()?;
let app_ctx = ctx.data::<Arc<dyn AppContextTrait>>()?;
let result = mutation_fn(resolver_ctx, app_ctx.clone(), input_object).await?;
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::<E>(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::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, 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)))
Ok(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::<E>(builder_context))
}),
))
}
pub fn generate_entity_default_create_one_mutation_fn<T, A>(
builder_context: &'static BuilderContext,
active_model_hooks: bool,
) -> CreateOneMutationFn<T::Model>
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync + IntoActiveModel<A>,
A: ActiveModelTrait<Entity = T> + 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::<T, A>(
&entity_input_builder,
&entity_object_builder,
&input_object,
resolve_context,
)
.map_err(SeaographyError::AsyncGraphQLError);
Box::pin(async move {
if active_model_hooks {
let transaction = app_ctx.db().begin().await?;
let active_model = active_model?;
let active_model = active_model.before_save(&transaction, true).await?;
let result: T::Model = active_model.insert(&transaction).await?;
let result = A::after_save(result, &transaction, true).await?;
transaction.commit().await?;
Ok(result)
} else {
let db = app_ctx.db();
let active_model = active_model?;
let result: T::Model = active_model.insert(db).await?;
Ok(result)
}
})
})
}
pub fn generate_entity_default_create_one_mutation_field<E, A>(
builder_context: &'static BuilderContext,
active_model_hooks: bool,
) -> Field
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
<E as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send + 'static,
<E as EntityTrait>::Model: Sync + IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send,
{
generate_entity_create_one_mutation_field::<E, TypeRef>(
builder_context,
None,
generate_entity_default_create_one_mutation_fn::<E, A>(builder_context, active_model_hooks),
)
let entity_create_one_mutation_builder = EntityCreateOneMutationBuilder {
context: builder_context,
};
entity_create_one_mutation_builder.to_field::<E, A>(active_model_hooks)
}
pub fn generate_entity_create_batch_mutation_field<E, ID>(
builder_context: &'static BuilderContext,
input_data_type_ref: Option<ID>,
mutation_fn: CreateBatchMutationFn<E::Model>,
) -> Field
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
ID: Into<TypeRef>,
{
let object_name: String = get_entity_name::<E>(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::<E>(builder_context),
TypeRef::named_nn_list_nn(get_entity_basic_type_name::<E>(builder_context)),
move |ctx| {
let entity_create_batch_mutation_builder = EntityCreateBatchMutationBuilder {
context: builder_context,
};
entity_create_batch_mutation_builder.to_field_with_mutation_fn::<E>(Arc::new(
move |resolver_ctx, input_objects| {
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::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(async_graphql::Error::new(
"Entity guard triggered.",
)),
};
}
Box::pin(async move {
let app_ctx = resolver_ctx.data::<Arc<dyn AppContextTrait>>()?;
let mut input_objects: Vec<ObjectAccessor<'_>> = 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::<E>(
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::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Field guard triggered."),
),
};
}
}
input_objects.push(input_object);
}
let app_ctx = ctx.data::<Arc<dyn AppContextTrait>>()?;
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::<E>(builder_context))
}),
))
}
pub fn generate_entity_default_create_batch_mutation_fn<E, A>(
builder_context: &'static BuilderContext,
active_model_hooks: bool,
) -> CreateBatchMutationFn<E::Model>
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
<E as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + 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::<E, A>(
&entity_input_builder,
&entity_object_builder,
&input_object,
resolve_context,
)
})
.collect::<Result<Vec<_>, _>>()
.map_err(SeaographyError::AsyncGraphQLError);
Box::pin(async move {
if active_model_hooks {
let transaction = app_ctx.db().begin().await?;
let mut before_save_models = vec![];
for active_model in active_models? {
let before_save_model = active_model.before_save(&transaction, false).await?;
before_save_models.push(before_save_model);
}
let models: Vec<E::Model> = 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?;
let result = mutation_fn(resolver_ctx, app_ctx.clone(), input_objects).await?;
Ok(result)
} else {
let db = app_ctx.db();
let active_models = active_models?;
let results: Vec<E::Model> = E::insert_many(active_models)
.exec_with_returning_many(db)
.await?;
Ok(results)
}
})
})
})
},
))
}
pub fn generate_entity_default_create_batch_mutation_field<E, A>(
@ -490,177 +239,45 @@ where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
<E as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send + 'static,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send,
{
generate_entity_create_batch_mutation_field::<E, TypeRef>(
builder_context,
None,
generate_entity_default_create_batch_mutation_fn::<E, A>(
builder_context,
active_model_hooks,
),
)
let entity_create_batch_mutation_builder = EntityCreateBatchMutationBuilder {
context: builder_context,
};
entity_create_batch_mutation_builder.to_field::<E, A>(active_model_hooks)
}
pub fn generate_entity_update_mutation_field<E, I>(
pub fn generate_entity_update_mutation_field<E>(
builder_context: &'static BuilderContext,
input_data_type_ref: Option<I>,
mutation_fn: UpdateMutationFn<E::Model>,
) -> Field
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
I: Into<TypeRef>,
{
let guard = builder_context
.guards
.entity_guards
.get(&get_entity_name::<E>(builder_context));
let field_guards = &builder_context.guards.field_guards;
Field::new(
get_entity_update_mutation_field_name::<E>(builder_context),
TypeRef::named_nn_list_nn(get_entity_basic_type_name::<E>(builder_context)),
move |ctx| {
let entity_update_mutation_builder = EntityUpdateMutationBuilder {
context: builder_context,
};
entity_update_mutation_builder.to_field_with_mutation_fn::<E>(Arc::new(
move |resolver_ctx, filters, input_object| {
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::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(async_graphql::Error::new(
"Entity guard triggered.",
)),
};
}
let app_ctx = ctx.data::<Arc<dyn AppContextTrait>>()?;
let filters = ctx.args.get(get_entity_update_mutation_filter_field_name(
builder_context,
));
let filter_condition = get_filter_conditions::<E>(&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::<E>(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::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, 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::<E>(builder_context))
}),
))
.argument(InputValue::new(
get_entity_update_mutation_filter_field_name(builder_context),
TypeRef::named(get_entity_filter_input_type_name::<E>(builder_context)),
))
}
pub fn generate_entity_default_update_mutation_fn<T, A>(
builder_context: &'static BuilderContext,
active_model_hooks: bool,
) -> UpdateMutationFn<T::Model>
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync + IntoActiveModel<A>,
A: ActiveModelTrait<Entity = T> + 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::<T, A>(
&entity_input_builder,
&entity_object_builder,
&input_object,
resolve_context,
)
.map_err(SeaographyError::AsyncGraphQLError);
Box::pin(async move {
if active_model_hooks {
let transaction = app_ctx.db().begin().await?;
let app_ctx = resolver_ctx.data::<Arc<dyn AppContextTrait>>()?;
let active_model = active_model?;
let result = mutation_fn(
resolver_ctx,
app_ctx.clone(),
get_filter_conditions::<E>(resolver_ctx, builder_context, filters),
input_object,
)
.await
.map_err(async_graphql::Error::new_with_source)?;
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)
}
Ok(result)
})
},
)
))
}
pub fn generate_entity_default_update_mutation_field<E, A>(
@ -671,13 +288,12 @@ where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
<E as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send + 'static,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send,
{
generate_entity_update_mutation_field::<E, TypeRef>(
builder_context,
None,
generate_entity_default_update_mutation_fn::<E, A>(builder_context, active_model_hooks),
)
let entity_update_mutation_builder = EntityUpdateMutationBuilder {
context: builder_context,
};
entity_update_mutation_builder.to_field::<E, A>(active_model_hooks)
}
pub fn generate_entity_delete_mutation_field<E>(
@ -688,111 +304,42 @@ where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
{
let object_name: String = get_entity_name::<E>(builder_context);
let guard = builder_context.guards.entity_guards.get(&object_name);
Field::new(
get_entity_delete_mutation_field_name::<E>(builder_context),
TypeRef::named_nn(TypeRef::INT),
move |ctx| {
let entity_delete_mutation_builder = EntityDeleteMutationBuilder {
context: builder_context,
};
entity_delete_mutation_builder.to_field_with_mutation_fn::<E>(Arc::new(
move |resolver_ctx, filters| {
let mutation_fn = mutation_fn.clone();
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
} else {
GuardAction::Allow
};
if let GuardAction::Block(reason) = guard_flag {
return Err::<Option<_>, async_graphql::Error>(async_graphql::Error::new(
reason.unwrap_or("Entity guard triggered.".into()),
));
}
Box::pin(async move {
let app_ctx = resolver_ctx.data::<Arc<dyn AppContextTrait>>()?;
let result = mutation_fn(
resolver_ctx,
app_ctx.clone(),
get_filter_conditions::<E>(resolver_ctx, builder_context, filters),
)
.await
.map_err(async_graphql::Error::new_with_source)?;
let filters = ctx.args.get(get_entity_delete_mutation_filter_field_name(
builder_context,
));
let filter_condition = get_filter_conditions::<E>(&ctx, builder_context, filters);
let app_ctx = ctx.data::<Arc<dyn AppContextTrait>>()?;
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)))
Ok(result)
})
},
)
.argument(InputValue::new(
get_entity_delete_mutation_filter_field_name(builder_context),
TypeRef::named(get_entity_filter_input_type_name::<E>(builder_context)),
))
}
pub fn generate_entity_default_delete_mutation_fn<E, A>(
_builder_context: &'static BuilderContext,
active_model_hooks: bool,
) -> DeleteMutationFn
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
<E as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + 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::Model> = E::find()
.filter(filter_condition.clone())
.all(&transaction)
.await?;
let mut active_models: Vec<A> = 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<E, A>(
builder_context: &'static BuilderContext,
active_model_hooks: bool,
) -> Field
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync,
<E as EntityTrait>::Model: IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send + 'static,
<E as EntityTrait>::Model: Sync + IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send,
{
generate_entity_delete_mutation_field::<E>(
builder_context,
generate_entity_default_delete_mutation_fn::<E, A>(builder_context, active_model_hooks),
)
let entity_delete_mutation_builder = EntityDeleteMutationBuilder {
context: builder_context,
};
entity_delete_mutation_builder.to_field::<E, A>(active_model_hooks)
}
pub fn register_entity_default_mutations<E, A>(
@ -802,30 +349,31 @@ pub fn register_entity_default_mutations<E, A>(
where
E: EntityTrait,
<E as EntityTrait>::Model: Sync + IntoActiveModel<A>,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send + 'static,
A: ActiveModelTrait<Entity = E> + sea_orm::ActiveModelBehavior + std::marker::Send,
{
let builder_context = builder.context;
builder
.outputs
.push(generate_entity_default_basic_entity_object::<E>(
builder.context,
builder_context,
));
builder.inputs.extend([
generate_entity_default_insert_input_object::<E>(builder.context),
generate_entity_default_update_input_object::<E>(builder.context),
generate_entity_default_insert_input_object::<E>(builder_context),
generate_entity_default_update_input_object::<E>(builder_context),
]);
builder.mutations.extend([
generate_entity_default_create_one_mutation_field::<E, A>(
builder.context,
builder_context,
active_model_hooks,
),
generate_entity_default_create_batch_mutation_field::<E, A>(
builder.context,
builder_context,
active_model_hooks,
),
generate_entity_default_update_mutation_field::<E, A>(builder.context, active_model_hooks),
generate_entity_default_delete_mutation_field::<E, A>(builder.context, active_model_hooks),
generate_entity_default_update_mutation_field::<E, A>(builder_context, active_model_hooks),
generate_entity_default_delete_mutation_field::<E, A>(builder_context, active_model_hooks),
]);
builder
@ -859,7 +407,7 @@ pub(crate) fn register_entity_default_writable_impl<T, RE, A, I>(
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync + IntoActiveModel<A>,
A: ActiveModelTrait<Entity = T> + sea_orm::ActiveModelBehavior + std::marker::Send + 'static,
A: ActiveModelTrait<Entity = T> + sea_orm::ActiveModelBehavior + std::marker::Send,
RE: sea_orm::Iterable<Iterator = I> + RelationBuilder,
I: Iterator<Item = RE> + Clone + DoubleEndedIterator + ExactSizeIterator + FusedIterator,
{

View File

@ -911,18 +911,15 @@ where
Box::new(
move |_resolve_context: &ResolverContext<'_>, condition, filter| {
if let Some(filter) = filter {
let filter_value = to_value(filter.as_index_map()).map_err(|e| {
SeaographyError::AsyncGraphQLError(GraphqlError::new_with_source(e))
})?;
let filter_value =
to_value(filter.as_index_map()).map_err(GraphqlError::new_with_source)?;
let filter_json: JsonValue = filter_value.into_json().map_err(|e| {
SeaographyError::AsyncGraphQLError(GraphqlError::new(format!("{e:?}")))
})?;
let filter_json: JsonValue = filter_value
.into_json()
.map_err(GraphqlError::new_with_source)?;
let cond_where = prepare_jsonb_filter_input(&Expr::col(column), filter_json)
.map_err(|e| {
SeaographyError::AsyncGraphQLError(GraphqlError::new_with_source(e))
})?;
.map_err(GraphqlError::new_with_source)?;
let condition = condition.add(cond_where);
Ok(condition)
@ -946,14 +943,22 @@ where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_column_name = get_entity_and_column_name::<T>(context, column);
context.filter_types.overwrites.insert(
get_entity_and_column_name::<T>(context, column),
Some(FilterType::Custom(JSONB_FILTER_NAME.to_string())),
);
context.filter_types.condition_functions.insert(
entity_column_name.clone(),
generate_jsonb_filter_condition_function::<T>(context, column),
);
}
pub fn validate_jsonb_input_for_entity<T, S>(context: &mut BuilderContext, column: &T::Column)
where
pub fn try_convert_jsonb_input_for_entity<T, S>(
context: &mut BuilderContext,
column: &T::Column,
case: Option<Case<'static>>,
) where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
S: DeserializeOwned + Serialize,
@ -962,49 +967,52 @@ where
context.types.input_conversions.insert(
entity_column_name.clone(),
Box::new(move |_resolve_context, accessor| {
let deserialized = accessor.deserialize::<S>().map_err(|err| {
SeaographyError::TypeConversionError(
err.message,
format!("Json - {entity_column_name}"),
)
})?;
let json_value = serde_json::to_value(deserialized).map_err(|err| {
let mut json_value: serde_json::Value = accessor.deserialize()?;
if let Some(case) = case {
json_value = convert_json_keys(json_value, case);
}
serde_json::from_value::<S>(json_value.clone()).map_err(|err| {
SeaographyError::TypeConversionError(
err.to_string(),
format!("Json - {entity_column_name}"),
)
})?;
Ok(sea_orm::Value::Json(Some(Box::new(json_value))))
}),
);
}
pub fn convert_jsonb_output_case_for_entity<T>(
pub fn convert_jsonb_output_for_entity<T>(
context: &mut BuilderContext,
column: &T::Column,
case: Case<'static>,
case: Option<Case<'static>>,
) where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,
{
let entity_column_key = get_entity_and_column_name::<T>(context, column);
let entity_column_name = get_entity_and_column_name::<T>(context, column);
context.types.output_conversions.insert(
entity_column_key.clone(),
entity_column_name.clone(),
Box::new(move |value| {
if let sea_orm::Value::Json(Some(json)) = value {
let result =
async_graphql::Value::from_json(convert_json_keys(json.as_ref().clone(), case))
.map_err(|err| {
SeaographyError::TypeConversionError(
err.to_string(),
format!("Json - {entity_column_key}"),
)
})?;
let mut json_value = json.as_ref().clone();
if let Some(case) = case {
json_value = convert_json_keys(json_value, case);
}
let result = async_graphql::Value::from_json(json_value).map_err(|err| {
SeaographyError::TypeConversionError(
err.to_string(),
format!("Json - {entity_column_name}"),
)
})?;
Ok(result)
} else {
Err(SeaographyError::TypeConversionError(
"value should be json".to_string(),
format!("Json - {entity_column_key}"),
format!("Json - {entity_column_name}"),
))
}
}),

View File

@ -78,7 +78,7 @@ where
context.filter_input.type_name.as_ref()(&entity_name)
}
pub fn get_entity_insert_data_input_type_name<T>(context: &BuilderContext) -> String
pub fn get_entity_insert_input_type_name<T>(context: &BuilderContext) -> String
where
T: EntityTrait,
<T as 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<T>(context: &BuilderContext) -> String
pub fn get_entity_update_input_type_name<T>(context: &BuilderContext) -> String
where
T: EntityTrait,
<T as EntityTrait>::Model: Sync,

View File

@ -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)

View File

@ -1,9 +1,10 @@
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,
SubscriberTask, SubscriberTaskInput, SubscriberTaskType, SubscriberTaskTypeEnum,
SubscriberTaskTypeVariant, SubscriberTaskTypeVariantIter,
};
#[derive(Clone, Debug, PartialEq, Eq, DeriveActiveEnum, EnumIter, DeriveDisplay)]
@ -84,4 +85,19 @@ pub enum RelatedEntity {
}
#[async_trait]
impl ActiveModelBehavior for ActiveModel {}
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C>(mut self, _db: &C, _insert: bool) -> Result<Self, DbErr>
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)
}
}

View File

@ -42,9 +42,13 @@ where
}
pub trait SubscriberTaskTrait: AsyncTaskTrait {
type InputType: Serialize + DeserializeOwned + Sized + Send;
fn get_subscriber_id(&self) -> i32;
fn get_cron_id(&self) -> Option<i32>;
fn from_input(input: Self::InputType, subscriber_id: i32) -> Self;
}
pub trait SystemTaskTrait: AsyncTaskTrait {}

View File

@ -12,9 +12,10 @@ pub use core::{
pub use config::TaskConfig;
pub use r#extern::{ApalisJobs, ApalisSchema};
pub use registry::{
OptimizeImageTask, SubscriberTask, SubscriberTaskType, SubscriberTaskTypeEnum,
SubscriberTaskTypeVariant, SubscriberTaskTypeVariantIter, SyncOneSubscriptionFeedsFullTask,
SyncOneSubscriptionFeedsIncrementalTask, SyncOneSubscriptionSourcesTask, SystemTask,
SystemTaskType, SystemTaskTypeEnum, SystemTaskTypeVariant, SystemTaskTypeVariantIter,
OptimizeImageTask, SubscriberTask, SubscriberTaskInput, SubscriberTaskType,
SubscriberTaskTypeEnum, SubscriberTaskTypeVariant, SubscriberTaskTypeVariantIter,
SyncOneSubscriptionFeedsFullTask, SyncOneSubscriptionFeedsIncrementalTask,
SyncOneSubscriptionSourcesTask, SystemTask, SystemTaskType, SystemTaskTypeEnum,
SystemTaskTypeVariant, SystemTaskTypeVariantIter,
};
pub use service::TaskService;

View File

@ -2,8 +2,8 @@ mod subscriber;
mod system;
pub use subscriber::{
SubscriberTask, SubscriberTaskType, SubscriberTaskTypeEnum, SubscriberTaskTypeVariant,
SubscriberTaskTypeVariantIter, SyncOneSubscriptionFeedsFullTask,
SubscriberTask, SubscriberTaskInput, SubscriberTaskType, SubscriberTaskTypeEnum,
SubscriberTaskTypeVariant, SubscriberTaskTypeVariantIter, SyncOneSubscriptionFeedsFullTask,
SyncOneSubscriptionFeedsIncrementalTask, SyncOneSubscriptionSourcesTask,
};
pub use system::{

View File

@ -6,15 +6,36 @@ macro_rules! register_subscriber_task_type {
}
) => {
$(#[$type_meta])*
#[derive(typed_builder::TypedBuilder)]
#[derive(typed_builder::TypedBuilder, ts_rs::TS, serde::Serialize, serde::Deserialize)]
#[ts(export, rename_all = "camelCase")]
$task_vis struct $task_name {
$($(#[$field_meta])* pub $field_name: $field_type,)*
pub subscriber_id: i32,
#[builder(default = None)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cron_id: Option<i32>,
}
paste::paste! {
$(#[$type_meta])*
#[derive(ts_rs::TS, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
#[ts(export, rename_all = "camelCase")]
$task_vis struct [<$task_name Input>] {
$($(#[$field_meta])* pub $field_name: $field_type,)*
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subscriber_id: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cron_id: Option<i32>,
}
}
impl $crate::task::SubscriberTaskTrait for $task_name {
paste::paste! {
type InputType = [<$task_name Input>];
}
fn get_subscriber_id(&self) -> i32 {
self.subscriber_id
}
@ -22,6 +43,14 @@ macro_rules! register_subscriber_task_type {
fn get_cron_id(&self) -> Option<i32> {
self.cron_id
}
fn from_input(input: Self::InputType, subscriber_id: i32) -> Self {
Self {
$($field_name: input.$field_name,)*
cron_id: input.cron_id,
subscriber_id: input.subscriber_id.unwrap_or(subscriber_id),
}
}
}
}
}

View File

@ -2,7 +2,6 @@ mod base;
mod subscription;
use sea_orm::{DeriveActiveEnum, DeriveDisplay, EnumIter, FromJsonQueryResult};
use serde::{Deserialize, Serialize};
pub use subscription::{
SyncOneSubscriptionFeedsFullTask, SyncOneSubscriptionFeedsIncrementalTask,
SyncOneSubscriptionSourcesTask,
@ -12,7 +11,7 @@ macro_rules! register_subscriber_task_types {
(
task_type_enum: {
$(#[$type_enum_meta:meta])*
pub enum $type_enum_name:ident {
$type_vis:vis enum $type_enum_name:ident {
$(
$(#[$variant_meta:meta])*
$variant:ident => $string_value:literal
@ -21,7 +20,7 @@ macro_rules! register_subscriber_task_types {
},
task_enum: {
$(#[$task_enum_meta:meta])*
pub enum $task_enum_name:ident {
$task_vis:vis enum $task_enum_name:ident {
$(
$(#[$task_variant_meta:meta])*
$task_variant:ident($task_type:ty)
@ -30,8 +29,9 @@ macro_rules! register_subscriber_task_types {
}
) => {
$(#[$type_enum_meta])*
#[derive(serde::Serialize, serde::Deserialize)]
#[sea_orm(rs_type = "String", db_type = "Text")]
pub enum $type_enum_name {
$type_vis enum $type_enum_name {
$(
$(#[$variant_meta])*
#[serde(rename = $string_value)]
@ -42,8 +42,10 @@ macro_rules! register_subscriber_task_types {
$(#[$task_enum_meta])*
#[derive(ts_rs::TS, serde::Serialize, serde::Deserialize)]
#[serde(tag = "task_type")]
pub enum $task_enum_name {
#[ts(export,rename = "SubscriberTaskType", rename_all = "camelCase", tag = "taskType")]
$task_vis enum $task_enum_name {
$(
$(#[$task_variant_meta])*
#[serde(rename = $string_value)]
@ -51,6 +53,20 @@ macro_rules! register_subscriber_task_types {
)*
}
paste::paste! {
$(#[$task_enum_meta])*
#[derive(ts_rs::TS, serde::Serialize, serde::Deserialize)]
#[serde(tag = "taskType", rename_all = "camelCase")]
#[ts(export,rename_all = "camelCase", tag = "taskType")]
$task_vis enum [<$task_enum_name Input>] {
$(
$(#[$task_variant_meta])*
#[serde(rename = $string_value)]
$task_variant(<$task_type as $crate::task::SubscriberTaskTrait>::InputType),
)*
}
}
impl TryFrom<$task_enum_name> for serde_json::Value {
type Error = $crate::errors::RecorderError;
@ -87,6 +103,10 @@ macro_rules! register_subscriber_task_types {
}
impl $crate::task::SubscriberTaskTrait for $task_enum_name {
paste::paste! {
type InputType = [<$task_enum_name Input>];
}
fn get_subscriber_id(&self) -> i32 {
match self {
$(Self::$task_variant(t) =>
@ -100,6 +120,14 @@ macro_rules! register_subscriber_task_types {
<$task_type as $crate::task::SubscriberTaskTrait>::get_cron_id(t),)*
}
}
fn from_input(input: Self::InputType, subscriber_id: i32) -> Self {
match input {
$(Self::InputType::$task_variant(t) =>
Self::$task_variant(<$task_type as $crate::task::SubscriberTaskTrait>::from_input(t, subscriber_id)),)*
}
}
}
$(
@ -117,8 +145,6 @@ register_subscriber_task_types!(
#[derive(
Clone,
Debug,
Serialize,
Deserialize,
PartialEq,
Eq,
Copy,
@ -133,7 +159,7 @@ register_subscriber_task_types!(
}
},
task_enum: {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, FromJsonQueryResult)]
#[derive(Clone, Debug, PartialEq, Eq, FromJsonQueryResult)]
pub enum SubscriberTask {
SyncOneSubscriptionFeedsIncremental(SyncOneSubscriptionFeedsIncrementalTask),
SyncOneSubscriptionFeedsFull(SyncOneSubscriptionFeedsFullTask),

View File

@ -1,5 +1,4 @@
use sea_orm::prelude::*;
use serde::{Deserialize, Serialize};
use super::base::register_subscriber_task_type;
use crate::{errors::RecorderResult, models::subscriptions::SubscriptionTrait};
@ -40,7 +39,7 @@ macro_rules! register_subscription_task_type {
}
register_subscription_task_type! {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyncOneSubscriptionFeedsIncrementalTask {
} => async |subscription, ctx| -> RecorderResult<()> {
subscription.sync_feeds_incremental(ctx).await?;
@ -49,7 +48,7 @@ register_subscription_task_type! {
}
register_subscription_task_type! {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyncOneSubscriptionFeedsFullTask {
} => async |subscription, ctx| -> RecorderResult<()> {
subscription.sync_feeds_full(ctx).await?;
@ -58,7 +57,7 @@ register_subscription_task_type! {
}
register_subscription_task_type! {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyncOneSubscriptionSourcesTask {
} => async |subscription, ctx| -> RecorderResult<()> {
subscription.sync_sources(ctx).await?;

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"composite": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["bindings"]
}

View File

@ -12,6 +12,12 @@ const config: CodegenConfig = {
},
config: {
enumsAsConst: true,
scalars: {
SubscriberTaskType: {
input: 'recorder/bindings/SubscriberTaskInput#SubscriberTaskInput',
output: 'recorder/bindings/SubscriberTaskType#SubscriberTaskType',
},
},
},
},
},

View File

@ -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",

View File

@ -89,6 +89,7 @@ query GetSubscriptionDetail ($id: Int!) {
} }) {
nodes {
id
subscriberId
displayName
createdAt
updatedAt
@ -137,30 +138,6 @@ query GetSubscriptionDetail ($id: Int!) {
}
`;
export const SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL = gql`
mutation SyncSubscriptionFeedsIncremental($filter: SubscriptionsFilterInput!) {
subscriptionsSyncOneFeedsIncremental(filter: $filter) {
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({

View File

@ -1,10 +1,5 @@
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';
export const GET_TASKS = gql`
query GetTasks($filter: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {
@ -25,7 +20,11 @@ export const GET_TASKS = gql`
lockAt,
lockBy,
doneAt,
priority
priority,
subscription {
displayName
sourceUrl
}
}
paginationInfo {
total
@ -35,6 +34,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 +67,4 @@ 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 TaskDto = Omit<
GetTasksQuery['subscriberTasks']['nodes'][number],
'job'
> & {
job: TaskTypedDto;
};
export type TaskDto = GetTasksQuery['subscriberTasks']['nodes'][number];

View File

@ -0,0 +1,7 @@
import { AuthService } from '@/domains/auth/auth.service';
import { Injectable, inject } from '@outposts/injection-js';
@Injectable()
export class SubscriberService {
authService = inject(AuthService);
}

View File

@ -26,11 +26,9 @@ type Documents = {
"\n mutation InsertSubscription($data: SubscriptionsInsertInput!) {\n subscriptionsCreateOne(data: $data) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n credentialId\n }\n }\n": typeof types.InsertSubscriptionDocument,
"\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 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($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($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,
"\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 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 subscription {\n displayName\n sourceUrl\n }\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,
};
@ -47,11 +45,9 @@ const documents: Documents = {
"\n mutation InsertSubscription($data: SubscriptionsInsertInput!) {\n subscriptionsCreateOne(data: $data) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n credentialId\n }\n }\n": types.InsertSubscriptionDocument,
"\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 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($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($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,
"\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 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 subscription {\n displayName\n sourceUrl\n }\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,
};
@ -121,23 +117,15 @@ 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 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 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"];
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($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"];
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 subscription {\n displayName\n sourceUrl\n }\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 subscription {\n displayName\n sourceUrl\n }\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 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"];
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.
*/

View File

@ -1,4 +1,6 @@
/* eslint-disable */
import { SubscriberTaskInput } from 'recorder/bindings/SubscriberTaskInput';
import { SubscriberTaskType } from 'recorder/bindings/SubscriberTaskType';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
@ -17,6 +19,8 @@ export type Scalars = {
/** The `JSON` scalar type represents raw JSON values */
Json: { input: any; output: any; }
JsonbFilterInput: { input: any; output: any; }
/** type SubscriberTaskType = { "taskType": "sync_one_subscription_feeds_incremental" } & SyncOneSubscriptionFeedsIncrementalTask | { "taskType": "sync_one_subscription_feeds_full" } & SyncOneSubscriptionFeedsFullTask | { "taskType": "sync_one_subscription_sources" } & SyncOneSubscriptionSourcesTask; */
SubscriberTaskType: { input: SubscriberTaskInput; output: SubscriberTaskType; }
};
export type Bangumi = {
@ -350,7 +354,7 @@ export type Cron = {
status: CronStatusEnum;
subscriber?: Maybe<Subscribers>;
subscriberId?: Maybe<Scalars['Int']['output']>;
subscriberTask?: Maybe<Scalars['Json']['output']>;
subscriberTask?: Maybe<Scalars['SubscriberTaskType']['output']>;
subscription?: Maybe<Subscriptions>;
subscriptionId?: Maybe<Scalars['Int']['output']>;
timeoutMs: Scalars['Int']['output'];
@ -373,7 +377,7 @@ export type CronBasic = {
priority: Scalars['Int']['output'];
status: CronStatusEnum;
subscriberId?: Maybe<Scalars['Int']['output']>;
subscriberTask?: Maybe<Scalars['Json']['output']>;
subscriberTask?: Maybe<Scalars['SubscriberTaskType']['output']>;
subscriptionId?: Maybe<Scalars['Int']['output']>;
timeoutMs: Scalars['Int']['output'];
updatedAt: Scalars['String']['output'];
@ -419,9 +423,8 @@ export type CronFilterInput = {
export type CronInsertInput = {
cronExpr: Scalars['String']['input'];
enabled?: InputMaybe<Scalars['Boolean']['input']>;
id?: InputMaybe<Scalars['Int']['input']>;
maxAttempts?: InputMaybe<Scalars['Int']['input']>;
subscriberTask?: InputMaybe<Scalars['Json']['input']>;
subscriberTask?: InputMaybe<Scalars['SubscriberTaskType']['input']>;
timeoutMs?: InputMaybe<Scalars['Int']['input']>;
};
@ -1166,9 +1169,6 @@ export type Mutation = {
subscriptionsCreateBatch: Array<SubscriptionsBasic>;
subscriptionsCreateOne: SubscriptionsBasic;
subscriptionsDelete: Scalars['Int']['output'];
subscriptionsSyncOneFeedsFull: SubscriberTasksBasic;
subscriptionsSyncOneFeedsIncremental: SubscriberTasksBasic;
subscriptionsSyncOneSources: SubscriberTasksBasic;
subscriptionsUpdate: Array<SubscriptionsBasic>;
};
@ -1397,21 +1397,6 @@ export type MutationSubscriptionsDeleteArgs = {
};
export type MutationSubscriptionsSyncOneFeedsFullArgs = {
filter?: InputMaybe<SubscriptionsFilterInput>;
};
export type MutationSubscriptionsSyncOneFeedsIncrementalArgs = {
filter?: InputMaybe<SubscriptionsFilterInput>;
};
export type MutationSubscriptionsSyncOneSourcesArgs = {
filter?: InputMaybe<SubscriptionsFilterInput>;
};
export type MutationSubscriptionsUpdateArgs = {
data: SubscriptionsUpdateInput;
filter?: InputMaybe<SubscriptionsFilterInput>;
@ -1607,7 +1592,7 @@ export type SubscriberTasks = {
attempts: Scalars['Int']['output'];
doneAt?: Maybe<Scalars['String']['output']>;
id: Scalars['String']['output'];
job: Scalars['Json']['output'];
job: Scalars['SubscriberTaskType']['output'];
lastError?: Maybe<Scalars['String']['output']>;
lockAt?: Maybe<Scalars['String']['output']>;
lockBy?: Maybe<Scalars['String']['output']>;
@ -1627,7 +1612,7 @@ export type SubscriberTasksBasic = {
attempts: Scalars['Int']['output'];
doneAt?: Maybe<Scalars['String']['output']>;
id: Scalars['String']['output'];
job: Scalars['Json']['output'];
job: Scalars['SubscriberTaskType']['output'];
lastError?: Maybe<Scalars['String']['output']>;
lockAt?: Maybe<Scalars['String']['output']>;
lockBy?: Maybe<Scalars['String']['output']>;
@ -1674,10 +1659,7 @@ export type SubscriberTasksFilterInput = {
};
export type SubscriberTasksInsertInput = {
id?: InputMaybe<Scalars['String']['input']>;
job: Scalars['Json']['input'];
maxAttempts: Scalars['Int']['input'];
priority: Scalars['Int']['input'];
job: Scalars['SubscriberTaskType']['input'];
subscriberId?: InputMaybe<Scalars['Int']['input']>;
};
@ -2184,28 +2166,7 @@ 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, 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<{
filter: SubscriptionsFilterInput;
}>;
export type SyncSubscriptionFeedsIncrementalMutation = { __typename?: 'Mutation', subscriptionsSyncOneFeedsIncremental: { __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 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 GetTasksQueryVariables = Exact<{
filter: SubscriberTasksFilterInput;
@ -2214,7 +2175,14 @@ 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 GetTasksQuery = { __typename?: 'Query', subscriberTasks: { __typename?: 'SubscriberTasksConnection', nodes: Array<{ __typename?: 'SubscriberTasks', id: string, job: SubscriberTaskType, taskType: SubscriberTaskTypeEnum, status: SubscriberTaskStatusEnum, attempts: number, maxAttempts: number, runAt: string, lastError?: string | null, lockAt?: string | null, lockBy?: string | null, doneAt?: string | null, priority: number, subscription?: { __typename?: 'Subscriptions', displayName: string, sourceUrl: string } | null }>, 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;
@ -2228,7 +2196,7 @@ export type RetryTasksMutationVariables = Exact<{
}>;
export type RetryTasksMutation = { __typename?: 'Mutation', subscriberTasksRetryOne: { __typename?: 'SubscriberTasksBasic', 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 type RetryTasksMutation = { __typename?: 'Mutation', subscriberTasksRetryOne: { __typename?: 'SubscriberTasksBasic', id: string, job: SubscriberTaskType, 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":"filter"}},"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":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"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<GetCredential3rdQuery, GetCredential3rdQueryVariables>;
@ -2243,10 +2211,8 @@ export const GetSubscriptionsDocument = {"kind":"Document","definitions":[{"kind
export const InsertSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InsertSubscription"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsCreateOne"},"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":"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"}},{"kind":"Field","name":{"kind":"Name","value":"credentialId"}}]}}]}}]} as unknown as DocumentNode<InsertSubscriptionMutation, InsertSubscriptionMutationVariables>;
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<UpdateSubscriptionsMutation, UpdateSubscriptionsMutationVariables>;
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<DeleteSubscriptionsMutation, DeleteSubscriptionsMutationVariables>;
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":"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<GetSubscriptionDetailQuery, GetSubscriptionDetailQueryVariables>;
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<SyncSubscriptionFeedsIncrementalMutation, SyncSubscriptionFeedsIncrementalMutationVariables>;
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<SyncSubscriptionFeedsFullMutation, SyncSubscriptionFeedsFullMutationVariables>;
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<SyncSubscriptionSourcesMutation, SyncSubscriptionSourcesMutationVariables>;
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<GetTasksQuery, GetTasksQueryVariables>;
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<GetSubscriptionDetailQuery, GetSubscriptionDetailQueryVariables>;
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":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}}]}}]}},{"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<GetTasksQuery, GetTasksQueryVariables>;
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<InsertSubscriberTaskMutation, InsertSubscriberTaskMutationVariables>;
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<DeleteTasksMutation, DeleteTasksMutationVariables>;
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<RetryTasksMutation, RetryTasksMutationVariables>;

View File

@ -6,18 +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 {
SyncSubscriptionFeedsFullMutation,
SyncSubscriptionFeedsFullMutationVariables,
SyncSubscriptionFeedsIncrementalMutation,
SyncSubscriptionFeedsIncrementalMutationVariables,
SyncSubscriptionSourcesMutation,
SyncSubscriptionSourcesMutationVariables,
type InsertSubscriberTaskMutation,
type InsertSubscriberTaskMutationVariables,
SubscriberTaskTypeEnum,
} from '@/infra/graphql/gql/graphql';
import { useMutation } from '@apollo/client';
import { useNavigate } from '@tanstack/react-router';
@ -36,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.subscriptionsSyncOneFeedsIncremental);
},
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', {
@ -67,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 (
<div className="flex flex-col gap-2">
@ -90,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,
},
},
},
})
}
>
@ -102,8 +71,16 @@ export const SubscriptionSyncView = memo(
size="lg"
variant="outline"
onClick={() =>
syncSubscriptionFeedsIncremental({
variables: { filter: { id: { eq: id } } },
insertSubscriberTask({
variables: {
data: {
job: {
subscriptionId: id,
taskType:
SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsIncremental,
},
},
},
})
}
>
@ -114,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,
},
},
},
})
}
>

View File

@ -32,6 +32,7 @@ import {
} from '@tanstack/react-router';
import { format } from 'date-fns';
import { ArrowLeft, RefreshCw } from 'lucide-react';
import { useMemo } from 'react';
import { toast } from 'sonner';
import { prettyTaskType } from './-pretty-task-type';
import { getStatusBadge } from './-status-badge';
@ -104,6 +105,16 @@ function TaskDetailRouteComponent() {
},
});
const job = useMemo(() => {
if (!task) {
return null;
}
return {
...task.job,
subscription: task.subscription,
};
}, [task]);
if (loading) {
return <DetailCardSkeleton />;
}
@ -247,14 +258,14 @@ function TaskDetailRouteComponent() {
</div>
{/* Job Details */}
{task.job && (
{job && (
<>
<Separator />
<div className="space-y-2">
<Label className="font-medium text-sm">Job Details</Label>
<div className="rounded-md bg-muted p-3">
<pre className="overflow-x-auto whitespace-pre-wrap text-sm">
<code>{JSON.stringify(task.job, null, 2)}</code>
<code>{JSON.stringify(job, null, 2)}</code>
</pre>
</div>
</div>

View File

@ -11,5 +11,10 @@
"@/*": ["./src/*"]
}
},
"include": ["src"]
"include": ["src"],
"references": [
{
"path": "../../apps/recorder"
}
]
}

View File

@ -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
@ -27,7 +27,7 @@ dev-proxy:
pnpm run --parallel --filter=proxy dev
dev-recorder:
watchexec -r -e rs,toml,yaml,json,env -- cargo run -p recorder --bin recorder_cli -- --environment=development --graceful-shutdown=false
watchexec -r -e rs,toml,yaml,json -- cargo run -p recorder --bin recorder_cli -- --environment=development --graceful-shutdown=false
prod-recorder: prod-webui
cargo run --release -p recorder --bin recorder_cli -- --environment=production --working-dir=apps/recorder --graceful-shutdown=false

View File

@ -3,8 +3,9 @@ extern crate proc_macro;
use convert_case::{Case, Casing};
use darling::{FromDeriveInput, FromField, ast::Data, util::Ignored};
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{format_ident, quote};
use syn::{Attribute, DeriveInput, Generics, Ident, parse_macro_input};
use syn::{Attribute, DeriveInput, Generics, parse_macro_input};
#[derive(snafu::Snafu, Debug)]
enum GeneratorError {

5
pnpm-lock.yaml generated
View File

@ -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

View File

@ -4,9 +4,13 @@
{
"path": "./apps/email-playground"
},
{
"path": "./apps/recorder"
},
{
"path": "./apps/webui"
},
{
"path": "./packages/email"
},