From d2aab7369d38731405781a4a464dad089577b945 Mon Sep 17 00:00:00 2001 From: lonelyhentxi Date: Sun, 8 Jun 2025 00:36:59 +0800 Subject: [PATCH] fix: add sync subscription webui and check credential web ui --- Cargo.lock | 4 + apps/proxy/package.json | 1 - apps/recorder/.env | 3 + .../mikan_doppel_season_subscription.rs | 2 +- apps/recorder/recorder.config.toml | 8 + .../src/app/config/default_mixin.toml | 4 + apps/recorder/src/extract/mikan/client.rs | 32 ++-- apps/recorder/src/extract/mikan/config.rs | 2 +- .../src/extract/mikan/subscription.rs | 1 + apps/recorder/src/extract/mikan/web.rs | 20 +- apps/recorder/src/graphql/schema.rs | 6 +- .../src/graphql/views/credential_3rd.rs | 113 +++++++++++ apps/recorder/src/graphql/views/mod.rs | 2 + .../src/graphql/views/subscription.rs | 14 +- apps/recorder/src/models/credential_3rd.rs | 44 ++++- .../domains/recorder/schema/credential3rd.ts | 8 + .../domains/recorder/schema/subscriptions.ts | 24 +++ apps/webui/src/infra/errors/apollo.ts | 26 +++ apps/webui/src/infra/graphql/gql/gql.ts | 24 +++ apps/webui/src/infra/graphql/gql/graphql.ts | 77 ++++++-- .../src/infra/graphql/graphql.service.ts | 11 ++ apps/webui/src/presentation/routeTree.gen.ts | 93 +++++++++ .../_app/credential3rd/-check-available.tsx | 92 +++++++++ .../routes/_app/credential3rd/detail.$id.tsx | 19 +- .../routes/_app/credential3rd/edit.$id.tsx | 14 +- .../routes/_app/credential3rd/manage.tsx | 11 +- .../subscriptions/-credential3rd-select.tsx | 2 - .../routes/_app/subscriptions/-sync.tsx | 160 ++++++++++++++++ .../routes/_app/subscriptions/detail.$id.tsx | 71 +++++-- .../routes/_app/subscriptions/edit.$id.tsx | 10 +- .../routes/_app/subscriptions/manage.tsx | 20 +- .../routes/_app/tasks/detail.$id.tsx | 13 ++ .../presentation/routes/_app/tasks/manage.tsx | 13 ++ .../presentation/routes/_app/tasks/route.tsx | 8 + justfile | 7 +- package.json | 7 +- packages/downloader/src/bittorrent/source.rs | 2 +- packages/fetch/Cargo.toml | 2 + packages/fetch/src/client/core.rs | 58 ++++-- packages/fetch/src/client/error.rs | 21 +++ packages/fetch/src/client/mod.rs | 9 +- packages/fetch/src/client/proxy.rs | 56 ++++++ packages/util/Cargo.toml | 2 + packages/util/src/lib.rs | 2 + packages/util/src/loose.rs | 19 ++ pnpm-lock.yaml | 178 ++++++++++-------- 46 files changed, 1120 insertions(+), 195 deletions(-) create mode 100644 apps/recorder/src/graphql/views/credential_3rd.rs create mode 100644 apps/webui/src/infra/errors/apollo.ts create mode 100644 apps/webui/src/presentation/routes/_app/credential3rd/-check-available.tsx create mode 100644 apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx create mode 100644 apps/webui/src/presentation/routes/_app/tasks/detail.$id.tsx create mode 100644 apps/webui/src/presentation/routes/_app/tasks/manage.tsx create mode 100644 apps/webui/src/presentation/routes/_app/tasks/route.tsx create mode 100644 packages/fetch/src/client/error.rs create mode 100644 packages/fetch/src/client/proxy.rs create mode 100644 packages/util/src/loose.rs diff --git a/Cargo.lock b/Cargo.lock index e00faad..99f03f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2019,6 +2019,7 @@ dependencies = [ "http-cache", "http-cache-reqwest", "http-cache-semantics", + "http-serde", "lazy_static", "leaky-bucket", "moka", @@ -2031,6 +2032,7 @@ dependencies = [ "serde_json", "serde_with", "snafu", + "tracing", "url", "util", ] @@ -7701,6 +7703,8 @@ dependencies = [ "bytes", "futures", "quirks_path", + "serde", + "serde_with", "snafu", ] diff --git a/apps/proxy/package.json b/apps/proxy/package.json index 61e4676..3774547 100644 --- a/apps/proxy/package.json +++ b/apps/proxy/package.json @@ -9,7 +9,6 @@ "keywords": [], "license": "MIT", "devDependencies": { - "cross-env": "^7.0.3", "whistle": "^2.9.93" } } diff --git a/apps/recorder/.env b/apps/recorder/.env index b28612b..b0abc9b 100644 --- a/apps/recorder/.env +++ b/apps/recorder/.env @@ -11,3 +11,6 @@ BASIC_PASSWORD = "konobangu" # OIDC_EXTRA_SCOPES = "read:konobangu write:konobangu" # OIDC_EXTRA_CLAIM_KEY = "" # OIDC_EXTRA_CLAIM_VALUE = "" +# MIKAN_PROXY = "" +# MIKAN_PROXY_AUTH_HEADER = "" +# MIKAN_NO_PROXY = "" diff --git a/apps/recorder/examples/mikan_doppel_season_subscription.rs b/apps/recorder/examples/mikan_doppel_season_subscription.rs index 4cb1b4a..e2e68b6 100644 --- a/apps/recorder/examples/mikan_doppel_season_subscription.rs +++ b/apps/recorder/examples/mikan_doppel_season_subscription.rs @@ -65,7 +65,7 @@ async fn main() -> Result<()> { .prompt()?; let mikan_scrape_client = mikan_scrape_client - .fork_with_credential(UserPassCredential { + .fork_with_userpass_credential(UserPassCredential { username, password, user_agent: None, diff --git a/apps/recorder/recorder.config.toml b/apps/recorder/recorder.config.toml index 3ba2360..e7d8543 100644 --- a/apps/recorder/recorder.config.toml +++ b/apps/recorder/recorder.config.toml @@ -86,6 +86,14 @@ leaky_bucket_initial_tokens = 1 leaky_bucket_refill_tokens = 1 leaky_bucket_refill_interval = 500 + +[mikan.http_client.proxy] +server = '{{ get_env(name="MIKAN_PROXY", default = "") }}' +auth_header = '{{ get_env(name="MIKAN_PROXY_AUTH_HEADER", default = "") }}' +no_proxy = '{{ get_env(name="MIKAN_NO_PROXY", default = "") }}' +accept_invalid_certs = '{{ get_env(name="MIKAN_PROXY_ACCEPT_INVALID_CERTS", default = "false") }}' + + [auth] auth_type = '{{ get_env(name="AUTH_TYPE", default = "basic") }}' basic_user = '{{ get_env(name="BASIC_USER", default = "konobangu") }}' diff --git a/apps/recorder/src/app/config/default_mixin.toml b/apps/recorder/src/app/config/default_mixin.toml index 2ad992d..76d1987 100644 --- a/apps/recorder/src/app/config/default_mixin.toml +++ b/apps/recorder/src/app/config/default_mixin.toml @@ -11,6 +11,10 @@ leaky_bucket_initial_tokens = 0 leaky_bucket_refill_tokens = 1 leaky_bucket_refill_interval = 500 +[mikan.http_client.proxy] + +[mikan.http_client.proxy.headers] + [graphql] depth_limit = inf complexity_limit = inf diff --git a/apps/recorder/src/extract/mikan/client.rs b/apps/recorder/src/extract/mikan/client.rs index c03b255..cfdcece 100644 --- a/apps/recorder/src/extract/mikan/client.rs +++ b/apps/recorder/src/extract/mikan/client.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, ops::Deref, sync::Arc}; +use std::{fmt::Debug, ops::Deref}; use fetch::{HttpClient, HttpClientTrait}; use maplit::hashmap; @@ -136,7 +136,7 @@ impl MikanClient { pub async fn submit_credential_form( &self, - ctx: Arc, + ctx: &dyn AppContextTrait, subscriber_id: i32, credential_form: MikanCredentialForm, ) -> RecorderResult { @@ -149,7 +149,7 @@ impl MikanClient { subscriber_id: Set(subscriber_id), ..Default::default() } - .try_encrypt(ctx.clone()) + .try_encrypt(ctx) .await?; let credential: credential_3rd::Model = am.save(db).await?.try_into_model()?; @@ -158,8 +158,9 @@ impl MikanClient { pub async fn sync_credential_cookies( &self, - ctx: Arc, + ctx: &dyn AppContextTrait, credential_id: i32, + subscriber_id: i32, ) -> RecorderResult<()> { let cookies = self.http_client.save_cookie_store_to_json()?; if let Some(cookies) = cookies { @@ -167,19 +168,20 @@ impl MikanClient { cookies: Set(Some(cookies)), ..Default::default() } - .try_encrypt(ctx.clone()) + .try_encrypt(ctx) .await?; credential_3rd::Entity::update_many() .set(am) .filter(credential_3rd::Column::Id.eq(credential_id)) + .filter(credential_3rd::Column::SubscriberId.eq(subscriber_id)) .exec(ctx.db()) .await?; } Ok(()) } - pub async fn fork_with_credential( + pub async fn fork_with_userpass_credential( &self, userpass_credential: UserPassCredential, ) -> RecorderResult { @@ -204,10 +206,13 @@ impl MikanClient { pub async fn fork_with_credential_id( &self, - ctx: Arc, + ctx: &dyn AppContextTrait, credential_id: i32, + subscriber_id: i32, ) -> RecorderResult { - let credential = credential_3rd::Model::find_by_id(ctx.clone(), credential_id).await?; + let credential = + credential_3rd::Model::find_by_id_and_subscriber_id(ctx, credential_id, subscriber_id) + .await?; if let Some(credential) = credential { if credential.credential_type != Credential3rdType::Mikan { return Err(RecorderError::Credential3rdError { @@ -219,7 +224,8 @@ impl MikanClient { let userpass_credential: UserPassCredential = credential.try_into_userpass_credential(ctx)?; - self.fork_with_credential(userpass_credential).await + self.fork_with_userpass_credential(userpass_credential) + .await } else { Err(RecorderError::from_db_record_not_found( DbErr::RecordNotFound(format!("credential={credential_id} not found")), @@ -249,7 +255,7 @@ impl HttpClientTrait for MikanClient {} #[cfg(test)] mod tests { #![allow(unused_variables)] - use std::assert_matches::assert_matches; + use std::{assert_matches::assert_matches, sync::Arc}; use rstest::{fixture, rstest}; use tracing::Level; @@ -297,8 +303,10 @@ mod tests { let credential_form = build_testing_mikan_credential_form(); + let subscriber_id = 1; + let credential_model = mikan_client - .submit_credential_form(app_ctx.clone(), 1, credential_form.clone()) + .submit_credential_form(app_ctx.as_ref(), subscriber_id, credential_form.clone()) .await?; let expected_username = &credential_form.username; @@ -322,7 +330,7 @@ mod tests { ); let mikan_client = mikan_client - .fork_with_credential_id(app_ctx.clone(), credential_model.id) + .fork_with_credential_id(app_ctx.as_ref(), credential_model.id, subscriber_id) .await?; mikan_client.login().await?; diff --git a/apps/recorder/src/extract/mikan/config.rs b/apps/recorder/src/extract/mikan/config.rs index 83dc32c..4c3c1c7 100644 --- a/apps/recorder/src/extract/mikan/config.rs +++ b/apps/recorder/src/extract/mikan/config.rs @@ -2,7 +2,7 @@ use fetch::HttpClientConfig; use serde::{Deserialize, Serialize}; use url::Url; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct MikanConfig { pub http_client: HttpClientConfig, pub base_url: Url, diff --git a/apps/recorder/src/extract/mikan/subscription.rs b/apps/recorder/src/extract/mikan/subscription.rs index c59e27f..3ccb342 100644 --- a/apps/recorder/src/extract/mikan/subscription.rs +++ b/apps/recorder/src/extract/mikan/subscription.rs @@ -387,6 +387,7 @@ impl MikanSeasonSubscription { ctx, mikan_season_flow_url, credential_id, + self.get_subscriber_id(), ) } diff --git a/apps/recorder/src/extract/mikan/web.rs b/apps/recorder/src/extract/mikan/web.rs index 11901bc..70cc491 100644 --- a/apps/recorder/src/extract/mikan/web.rs +++ b/apps/recorder/src/extract/mikan/web.rs @@ -917,10 +917,11 @@ pub fn scrape_mikan_bangumi_meta_stream_from_season_flow_url( ctx: Arc, mikan_season_flow_url: Url, credential_id: i32, + subscriber_id: i32, ) -> impl Stream> { try_stream! { let mikan_base_url = ctx.mikan().base_url().clone(); - let mikan_client = ctx.mikan().fork_with_credential_id(ctx.clone(), credential_id).await?; + let mikan_client = ctx.mikan().fork_with_credential_id(ctx.as_ref(), credential_id, subscriber_id).await?; let content = fetch_html(&mikan_client, mikan_season_flow_url.clone()).await?; @@ -940,7 +941,7 @@ pub fn scrape_mikan_bangumi_meta_stream_from_season_flow_url( mikan_client - .sync_credential_cookies(ctx.clone(), credential_id) + .sync_credential_cookies(ctx.as_ref(), credential_id, subscriber_id) .await?; for bangumi_index in bangumi_indices_meta { @@ -969,7 +970,7 @@ pub fn scrape_mikan_bangumi_meta_stream_from_season_flow_url( } mikan_client - .sync_credential_cookies(ctx, credential_id) + .sync_credential_cookies(ctx.as_ref(), credential_id, subscriber_id) .await?; } } @@ -978,11 +979,13 @@ pub async fn scrape_mikan_bangumi_meta_list_from_season_flow_url( ctx: Arc, mikan_season_flow_url: Url, credential_id: i32, + subscriber_id: i32, ) -> RecorderResult> { let stream = scrape_mikan_bangumi_meta_stream_from_season_flow_url( ctx, mikan_season_flow_url, credential_id, + subscriber_id, ); pin_mut!(stream); @@ -1160,7 +1163,7 @@ mod test { let mikan_client = build_testing_mikan_client(mikan_base_url.clone()) .await? - .fork_with_credential(build_testing_mikan_credential()) + .fork_with_userpass_credential(build_testing_mikan_credential()) .await?; mikan_client.login().await?; @@ -1268,8 +1271,14 @@ mod test { let mikan_client = app_ctx.mikan(); + let subscriber_id = 1; + let credential = mikan_client - .submit_credential_form(app_ctx.clone(), 1, build_testing_mikan_credential_form()) + .submit_credential_form( + app_ctx.as_ref(), + subscriber_id, + build_testing_mikan_credential_form(), + ) .await?; let mikan_season_flow_url = @@ -1279,6 +1288,7 @@ mod test { app_ctx.clone(), mikan_season_flow_url, credential.id, + subscriber_id, ); pin_mut!(bangumi_meta_stream); diff --git a/apps/recorder/src/graphql/schema.rs b/apps/recorder/src/graphql/schema.rs index 7df60e3..f494c22 100644 --- a/apps/recorder/src/graphql/schema.rs +++ b/apps/recorder/src/graphql/schema.rs @@ -20,7 +20,7 @@ use crate::{ }, util::{get_entity_column_key, get_entity_key}, }, - views::register_subscriptions_to_schema, + views::{register_credential3rd_to_schema, register_subscriptions_to_schema}, }, }; @@ -156,7 +156,7 @@ pub fn build_schema( &mut context, &subscriber_tasks::Column::Job, ); - add_crypto_transformers(&mut context, app_ctx); + add_crypto_transformers(&mut context, app_ctx.clone()); for column in subscribers::Column::iter() { if !matches!(column, subscribers::Column::Id) { restrict_filter_input_for_entity::( @@ -215,6 +215,7 @@ pub fn build_schema( { builder = register_subscriptions_to_schema(builder); + builder = register_credential3rd_to_schema(builder); } let schema = builder.schema_builder(); @@ -231,6 +232,7 @@ pub fn build_schema( }; schema .data(database) + .data(app_ctx) .finish() .inspect_err(|e| tracing::error!(e = ?e)) } diff --git a/apps/recorder/src/graphql/views/credential_3rd.rs b/apps/recorder/src/graphql/views/credential_3rd.rs new file mode 100644 index 0000000..51fabff --- /dev/null +++ b/apps/recorder/src/graphql/views/credential_3rd.rs @@ -0,0 +1,113 @@ +use std::sync::Arc; + +use async_graphql::dynamic::{ + Field, FieldFuture, FieldValue, InputObject, InputValue, Object, TypeRef, +}; +use seaography::Builder as SeaographyBuilder; +use serde::{Deserialize, Serialize}; +use util_derive::DynamicGraphql; + +use crate::{ + app::AppContextTrait, auth::AuthUserInfo, errors::RecorderError, models::credential_3rd, +}; + +#[derive(DynamicGraphql, Serialize, Deserialize, Clone, Debug)] +struct Credential3rdCheckAvailableInput { + pub id: i32, +} + +impl Credential3rdCheckAvailableInput { + fn input_type_name() -> &'static str { + "Credential3rdCheckAvailableInput" + } + + fn arg_name() -> &'static str { + "filter" + } + + fn generate_input_object() -> InputObject { + InputObject::new(Self::input_type_name()) + .description("The input of the credential3rdCheckAvailable query") + .field(InputValue::new( + Credential3rdCheckAvailableInputFieldEnum::Id.as_str(), + TypeRef::named_nn(TypeRef::INT), + )) + } +} + +#[derive(DynamicGraphql, Serialize, Deserialize, Clone, Debug)] +pub struct Credential3rdCheckAvailableInfo { + pub available: bool, +} + +impl Credential3rdCheckAvailableInfo { + fn object_type_name() -> &'static str { + "Credential3rdCheckAvailableInfo" + } + + fn generate_output_object() -> Object { + Object::new(Self::object_type_name()) + .description("The output of the credential3rdCheckAvailable query") + .field(Field::new( + Credential3rdCheckAvailableInfoFieldEnum::Available, + TypeRef::named_nn(TypeRef::BOOLEAN), + move |ctx| { + FieldFuture::new(async move { + let subscription_info = ctx.parent_value.try_downcast_ref::()?; + Ok(Some(async_graphql::Value::from( + subscription_info.available, + ))) + }) + }, + )) + } +} + +pub fn register_credential3rd_to_schema(mut builder: SeaographyBuilder) -> SeaographyBuilder { + builder.schema = builder + .schema + .register(Credential3rdCheckAvailableInput::generate_input_object()); + builder.schema = builder + .schema + .register(Credential3rdCheckAvailableInfo::generate_output_object()); + + builder.queries.push( + Field::new( + "credential3rdCheckAvailable", + TypeRef::named_nn(Credential3rdCheckAvailableInfo::object_type_name()), + move |ctx| { + FieldFuture::new(async move { + let auth_user_info = ctx.data::()?; + let input: Credential3rdCheckAvailableInput = ctx + .args + .get(Credential3rdCheckAvailableInput::arg_name()) + .unwrap() + .deserialize()?; + let app_ctx = ctx.data::>()?; + + let credential_model = credential_3rd::Model::find_by_id_and_subscriber_id( + app_ctx.as_ref(), + input.id, + auth_user_info.subscriber_auth.subscriber_id, + ) + .await? + .ok_or_else(|| RecorderError::Credential3rdError { + message: format!("credential = {} not found", input.id), + source: None.into(), + })?; + + let available = credential_model.check_available(app_ctx.as_ref()).await?; + Ok(Some(FieldValue::owned_any( + Credential3rdCheckAvailableInfo { available }, + ))) + }) + }, + ) + .argument(InputValue::new( + Credential3rdCheckAvailableInput::arg_name(), + TypeRef::named_nn(Credential3rdCheckAvailableInput::input_type_name()), + )), + ); + + builder +} diff --git a/apps/recorder/src/graphql/views/mod.rs b/apps/recorder/src/graphql/views/mod.rs index d55adb4..07a4c82 100644 --- a/apps/recorder/src/graphql/views/mod.rs +++ b/apps/recorder/src/graphql/views/mod.rs @@ -1,3 +1,5 @@ +mod credential_3rd; mod subscription; +pub use credential_3rd::register_credential3rd_to_schema; pub use subscription::register_subscriptions_to_schema; diff --git a/apps/recorder/src/graphql/views/subscription.rs b/apps/recorder/src/graphql/views/subscription.rs index 597f5b8..eec0f7d 100644 --- a/apps/recorder/src/graphql/views/subscription.rs +++ b/apps/recorder/src/graphql/views/subscription.rs @@ -16,7 +16,7 @@ use crate::{ #[derive(DynamicGraphql, Serialize, Deserialize, Clone, Debug)] struct SyncOneSubscriptionFilterInput { - pub subscription_id: i32, + pub id: i32, } impl SyncOneSubscriptionFilterInput { @@ -32,7 +32,7 @@ impl SyncOneSubscriptionFilterInput { InputObject::new(Self::input_type_name()) .description("The input of the subscriptionSyncOne series of mutations") .field(InputValue::new( - SyncOneSubscriptionFilterInputFieldEnum::SubscriptionId.as_str(), + SyncOneSubscriptionFilterInputFieldEnum::Id.as_str(), TypeRef::named_nn(TypeRef::INT), )) } @@ -74,7 +74,7 @@ pub fn register_subscriptions_to_schema(mut builder: SeaographyBuilder) -> Seaog .schema .register(SyncOneSubscriptionInfo::generate_output_object()); - builder.queries.push( + builder.mutations.push( Field::new( "subscriptionSyncOneFeedsIncremental", TypeRef::named_nn(SyncOneSubscriptionInfo::object_type_name()), @@ -93,7 +93,7 @@ pub fn register_subscriptions_to_schema(mut builder: SeaographyBuilder) -> Seaog let subscription_model = subscriptions::Model::find_by_id_and_subscriber_id( app_ctx.as_ref(), - filter_input.subscription_id, + filter_input.id, subscriber_id, ) .await?; @@ -124,7 +124,7 @@ pub fn register_subscriptions_to_schema(mut builder: SeaographyBuilder) -> Seaog )), ); - builder.queries.push( + builder.mutations.push( Field::new( "subscriptionSyncOneFeedsFull", TypeRef::named_nn(SyncOneSubscriptionInfo::object_type_name()), @@ -143,7 +143,7 @@ pub fn register_subscriptions_to_schema(mut builder: SeaographyBuilder) -> Seaog let subscription_model = subscriptions::Model::find_by_id_and_subscriber_id( app_ctx.as_ref(), - filter_input.subscription_id, + filter_input.id, subscriber_id, ) .await?; @@ -193,7 +193,7 @@ pub fn register_subscriptions_to_schema(mut builder: SeaographyBuilder) -> Seaog let subscription_model = subscriptions::Model::find_by_id_and_subscriber_id( app_ctx.as_ref(), - filter_input.subscription_id, + filter_input.id, subscriber_id, ) .await?; diff --git a/apps/recorder/src/models/credential_3rd.rs b/apps/recorder/src/models/credential_3rd.rs index af91951..31c17a9 100644 --- a/apps/recorder/src/models/credential_3rd.rs +++ b/apps/recorder/src/models/credential_3rd.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use async_trait::async_trait; use sea_orm::{ActiveValue, prelude::*}; use serde::{Deserialize, Serialize}; @@ -79,7 +77,7 @@ pub enum RelatedEntity { impl ActiveModelBehavior for ActiveModel {} impl ActiveModel { - pub async fn try_encrypt(mut self, ctx: Arc) -> RecorderResult { + pub async fn try_encrypt(mut self, ctx: &dyn AppContextTrait) -> RecorderResult { let crypto = ctx.crypto(); if let ActiveValue::Set(Some(username)) = self.username { @@ -102,19 +100,24 @@ impl ActiveModel { } impl Model { - pub async fn find_by_id( - ctx: Arc, + pub async fn find_by_id_and_subscriber_id( + ctx: &dyn AppContextTrait, id: i32, + subscriber_id: i32, ) -> RecorderResult> { let db = ctx.db(); - let credential = Entity::find_by_id(id).one(db).await?; + let credential = Entity::find() + .filter(Column::Id.eq(id)) + .filter(Column::SubscriberId.eq(subscriber_id)) + .one(db) + .await?; Ok(credential) } pub fn try_into_userpass_credential( self, - ctx: Arc, + ctx: &dyn AppContextTrait, ) -> RecorderResult { let crypto = ctx.crypto(); let username_enc = self @@ -149,4 +152,31 @@ impl Model { user_agent: self.user_agent, }) } + + pub async fn check_available(self, ctx: &dyn AppContextTrait) -> RecorderResult { + let credential_id = self.id; + let subscriber_id = self.subscriber_id; + match self.credential_type { + Credential3rdType::Mikan => { + let mikan_client = { + let userpass_credential: UserPassCredential = + self.try_into_userpass_credential(ctx)?; + ctx.mikan() + .fork_with_userpass_credential(userpass_credential) + .await? + }; + let mut has_login = mikan_client.has_login().await?; + if !has_login { + mikan_client.login().await?; + has_login = true; + } + if has_login { + mikan_client + .sync_credential_cookies(ctx, credential_id, subscriber_id) + .await?; + } + Ok(has_login) + } + } + } } diff --git a/apps/webui/src/domains/recorder/schema/credential3rd.ts b/apps/webui/src/domains/recorder/schema/credential3rd.ts index 35c41ab..521ddb9 100644 --- a/apps/webui/src/domains/recorder/schema/credential3rd.ts +++ b/apps/webui/src/domains/recorder/schema/credential3rd.ts @@ -80,6 +80,14 @@ export const GET_CREDENTIAL_3RD_DETAIL = gql` } `; +export const CHECK_CREDENTIAL_3RD_AVAILABLE = gql` + query CheckCredential3rdAvailable($id: Int!) { + credential3rdCheckAvailable(filter: { id: $id }) { + available + } + } +`; + export const Credential3rdTypedMikanSchema = type({ credentialType: `'${Credential3rdTypeEnum.Mikan}'`, username: 'string > 0', diff --git a/apps/webui/src/domains/recorder/schema/subscriptions.ts b/apps/webui/src/domains/recorder/schema/subscriptions.ts index 85f7dee..19013e8 100644 --- a/apps/webui/src/domains/recorder/schema/subscriptions.ts +++ b/apps/webui/src/domains/recorder/schema/subscriptions.ts @@ -121,6 +121,30 @@ query GetSubscriptionDetail ($id: Int!) { } `; +export const SYNC_SUBSCRIPTION_FEEDS_INCREMENTAL = gql` + mutation SyncSubscriptionFeedsIncremental($id: Int!) { + subscriptionSyncOneFeedsIncremental(filter: { id: $id }) { + taskId + } + } +`; + +export const SYNC_SUBSCRIPTION_FEEDS_FULL = gql` + mutation SyncSubscriptionFeedsFull($id: Int!) { + subscriptionSyncOneFeedsFull(filter: { id: $id }) { + taskId + } + } +`; + +export const SYNC_SUBSCRIPTION_SOURCES = gql` + mutation SyncSubscriptionSources($id: Int!) { + subscriptionSyncOneSources(filter: { id: $id }) { + taskId + } + } +`; + export const SubscriptionTypedMikanSeasonSchema = MikanSubscriptionSeasonSourceUrlSchema.and( type({ diff --git a/apps/webui/src/infra/errors/apollo.ts b/apps/webui/src/infra/errors/apollo.ts new file mode 100644 index 0000000..dd79585 --- /dev/null +++ b/apps/webui/src/infra/errors/apollo.ts @@ -0,0 +1,26 @@ +import type { ApolloError, ApolloQueryResult } from '@apollo/client'; +import type { GraphQLFormattedError } from 'graphql'; + +export function getApolloQueryError( + response: ApolloQueryResult +): ApolloError | readonly GraphQLFormattedError[] | null { + if (response.error) { + return response.error; + } + if (response.errors) { + return response.errors; + } + return null; +} + +export function apolloErrorToMessage( + error: ApolloError | readonly GraphQLFormattedError[] +) { + if (Array.isArray(error)) { + return error.map((e) => e.message).join('\n'); + } + if ('message' in error) { + return error.message; + } + return 'Unknown error'; +} diff --git a/apps/webui/src/infra/graphql/gql/gql.ts b/apps/webui/src/infra/graphql/gql/gql.ts index f0e902a..d9c6a9b 100644 --- a/apps/webui/src/infra/graphql/gql/gql.ts +++ b/apps/webui/src/infra/graphql/gql/gql.ts @@ -19,11 +19,15 @@ type Documents = { "\n mutation UpdateCredential3rd($data: Credential3rdUpdateInput!, $filters: Credential3rdFilterInput!) {\n credential3rdUpdate(data: $data, filter: $filters) {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n }\n": typeof types.UpdateCredential3rdDocument, "\n mutation DeleteCredential3rd($filters: Credential3rdFilterInput!) {\n credential3rdDelete(filter: $filters)\n }\n": typeof types.DeleteCredential3rdDocument, "\n query GetCredential3rdDetail($id: Int!) {\n credential3rd(filters: { id: { eq: $id } }) {\n nodes {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n }\n }\n": typeof types.GetCredential3rdDetailDocument, + "\n query CheckCredential3rdAvailable($id: Int!) {\n credential3rdCheckAvailable(filter: { id: $id }) {\n available\n }\n }\n": typeof types.CheckCredential3rdAvailableDocument, "\n query GetSubscriptions($filters: SubscriptionsFilterInput!, $orderBy: SubscriptionsOrderInput!, $pagination: PaginationInput!) {\n subscriptions(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n credentialId\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": typeof types.GetSubscriptionsDocument, "\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 $filters: SubscriptionsFilterInput!,\n ) {\n subscriptionsUpdate (\n data: $data\n filter: $filters\n ) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n }\n}\n": typeof types.UpdateSubscriptionsDocument, "\n mutation DeleteSubscriptions($filters: SubscriptionsFilterInput) {\n subscriptionsDelete(filter: $filters)\n }\n": typeof types.DeleteSubscriptionsDocument, "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filters: { id: {\n eq: $id\n } }) {\n nodes {\n id\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n rawName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n savePath\n homepage\n }\n }\n }\n }\n}\n": typeof types.GetSubscriptionDetailDocument, + "\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsIncrementalDocument, + "\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsFullDocument, + "\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionSourcesDocument, }; const documents: Documents = { "\n query GetCredential3rd($filters: Credential3rdFilterInput!, $orderBy: Credential3rdOrderInput, $pagination: PaginationInput) {\n credential3rd(filters: $filters, orderBy: $orderBy, pagination: $pagination) {\n nodes {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": types.GetCredential3rdDocument, @@ -31,11 +35,15 @@ const documents: Documents = { "\n mutation UpdateCredential3rd($data: Credential3rdUpdateInput!, $filters: Credential3rdFilterInput!) {\n credential3rdUpdate(data: $data, filter: $filters) {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n }\n": types.UpdateCredential3rdDocument, "\n mutation DeleteCredential3rd($filters: Credential3rdFilterInput!) {\n credential3rdDelete(filter: $filters)\n }\n": types.DeleteCredential3rdDocument, "\n query GetCredential3rdDetail($id: Int!) {\n credential3rd(filters: { id: { eq: $id } }) {\n nodes {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n }\n }\n": types.GetCredential3rdDetailDocument, + "\n query CheckCredential3rdAvailable($id: Int!) {\n credential3rdCheckAvailable(filter: { id: $id }) {\n available\n }\n }\n": types.CheckCredential3rdAvailableDocument, "\n query GetSubscriptions($filters: SubscriptionsFilterInput!, $orderBy: SubscriptionsOrderInput!, $pagination: PaginationInput!) {\n subscriptions(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n credentialId\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": types.GetSubscriptionsDocument, "\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 $filters: SubscriptionsFilterInput!,\n ) {\n subscriptionsUpdate (\n data: $data\n filter: $filters\n ) {\n id\n createdAt\n updatedAt\n displayName\n category\n sourceUrl\n enabled\n }\n}\n": types.UpdateSubscriptionsDocument, "\n mutation DeleteSubscriptions($filters: SubscriptionsFilterInput) {\n subscriptionsDelete(filter: $filters)\n }\n": types.DeleteSubscriptionsDocument, "\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filters: { id: {\n eq: $id\n } }) {\n nodes {\n id\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n rawName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n savePath\n homepage\n }\n }\n }\n }\n}\n": types.GetSubscriptionDetailDocument, + "\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsIncrementalDocument, + "\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsFullDocument, + "\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionSourcesDocument, }; /** @@ -72,6 +80,10 @@ export function gql(source: "\n mutation DeleteCredential3rd($filters: Credenti * 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 GetCredential3rdDetail($id: Int!) {\n credential3rd(filters: { id: { eq: $id } }) {\n nodes {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\n }\n }\n }\n"): (typeof documents)["\n query GetCredential3rdDetail($id: Int!) {\n credential3rd(filters: { id: { eq: $id } }) {\n nodes {\n id\n cookies\n username\n password\n userAgent\n createdAt\n updatedAt\n credentialType\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 query CheckCredential3rdAvailable($id: Int!) {\n credential3rdCheckAvailable(filter: { id: $id }) {\n available\n }\n }\n"): (typeof documents)["\n query CheckCredential3rdAvailable($id: Int!) {\n credential3rdCheckAvailable(filter: { id: $id }) {\n available\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -92,6 +104,18 @@ export function gql(source: "\n mutation DeleteSubscriptions($filters: Subscr * 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(filters: { id: {\n eq: $id\n } }) {\n nodes {\n id\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n rawName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n savePath\n homepage\n }\n }\n }\n }\n}\n"): (typeof documents)["\nquery GetSubscriptionDetail ($id: Int!) {\n subscriptions(filters: { id: {\n eq: $id\n } }) {\n nodes {\n id\n displayName\n createdAt\n updatedAt\n category\n sourceUrl\n enabled\n credential3rd {\n id\n username\n }\n bangumi {\n nodes {\n createdAt\n updatedAt\n id\n mikanBangumiId\n displayName\n rawName\n season\n seasonRaw\n fansub\n mikanFansubId\n rssLink\n posterLink\n savePath\n homepage\n }\n }\n }\n }\n}\n"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n"): (typeof documents)["\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n"]; export function gql(source: string) { return (documents as any)[source] ?? {}; diff --git a/apps/webui/src/infra/graphql/gql/graphql.ts b/apps/webui/src/infra/graphql/gql/graphql.ts index 3922c3d..f939f54 100644 --- a/apps/webui/src/infra/graphql/gql/graphql.ts +++ b/apps/webui/src/infra/graphql/gql/graphql.ts @@ -216,6 +216,17 @@ export type Credential3rdBasic = { username?: Maybe; }; +/** The output of the credential3rdCheckAvailable query */ +export type Credential3rdCheckAvailableInfo = { + __typename?: 'Credential3rdCheckAvailableInfo'; + available: Scalars['Boolean']['output']; +}; + +/** The input of the credential3rdCheckAvailable query */ +export type Credential3rdCheckAvailableInput = { + id: Scalars['Int']['input']; +}; + export type Credential3rdConnection = { __typename?: 'Credential3rdConnection'; edges: Array; @@ -805,6 +816,8 @@ export type Mutation = { subscriptionEpisodeCreateOne: SubscriptionEpisodeBasic; subscriptionEpisodeDelete: Scalars['Int']['output']; subscriptionEpisodeUpdate: Array; + subscriptionSyncOneFeedsFull: SyncOneSubscriptionInfo; + subscriptionSyncOneFeedsIncremental: SyncOneSubscriptionInfo; subscriptionSyncOneSources: SyncOneSubscriptionInfo; subscriptionsCreateBatch: Array; subscriptionsCreateOne: SubscriptionsBasic; @@ -981,6 +994,16 @@ export type MutationSubscriptionEpisodeUpdateArgs = { }; +export type MutationSubscriptionSyncOneFeedsFullArgs = { + filter: SyncOneSubscriptionFilterInput; +}; + + +export type MutationSubscriptionSyncOneFeedsIncrementalArgs = { + filter: SyncOneSubscriptionFilterInput; +}; + + export type MutationSubscriptionSyncOneSourcesArgs = { filter: SyncOneSubscriptionFilterInput; }; @@ -1049,6 +1072,7 @@ export type Query = { _sea_orm_entity_metadata?: Maybe; bangumi: BangumiConnection; credential3rd: Credential3rdConnection; + credential3rdCheckAvailable: Credential3rdCheckAvailableInfo; downloaders: DownloadersConnection; downloads: DownloadsConnection; episodes: EpisodesConnection; @@ -1056,8 +1080,6 @@ export type Query = { subscribers: SubscribersConnection; subscriptionBangumi: SubscriptionBangumiConnection; subscriptionEpisode: SubscriptionEpisodeConnection; - subscriptionSyncOneFeedsFull: SyncOneSubscriptionInfo; - subscriptionSyncOneFeedsIncremental: SyncOneSubscriptionInfo; subscriptions: SubscriptionsConnection; }; @@ -1081,6 +1103,11 @@ export type QueryCredential3rdArgs = { }; +export type QueryCredential3rdCheckAvailableArgs = { + filter: Credential3rdCheckAvailableInput; +}; + + export type QueryDownloadersArgs = { filters?: InputMaybe; orderBy?: InputMaybe; @@ -1130,16 +1157,6 @@ export type QuerySubscriptionEpisodeArgs = { }; -export type QuerySubscriptionSyncOneFeedsFullArgs = { - filter: SyncOneSubscriptionFilterInput; -}; - - -export type QuerySubscriptionSyncOneFeedsIncrementalArgs = { - filter: SyncOneSubscriptionFilterInput; -}; - - export type QuerySubscriptionsArgs = { filters?: InputMaybe; orderBy?: InputMaybe; @@ -1616,7 +1633,7 @@ export type SubscriptionsUpdateInput = { /** The input of the subscriptionSyncOne series of mutations */ export type SyncOneSubscriptionFilterInput = { - subscriptionId: Scalars['Int']['input']; + id: Scalars['Int']['input']; }; /** The output of the subscriptionSyncOne series of mutations */ @@ -1678,6 +1695,13 @@ export type GetCredential3rdDetailQueryVariables = Exact<{ export type GetCredential3rdDetailQuery = { __typename?: 'Query', credential3rd: { __typename?: 'Credential3rdConnection', nodes: Array<{ __typename?: 'Credential3rd', id: number, cookies?: string | null, username?: string | null, password?: string | null, userAgent?: string | null, createdAt: string, updatedAt: string, credentialType: Credential3rdTypeEnum }> } }; +export type CheckCredential3rdAvailableQueryVariables = Exact<{ + id: Scalars['Int']['input']; +}>; + + +export type CheckCredential3rdAvailableQuery = { __typename?: 'Query', credential3rdCheckAvailable: { __typename?: 'Credential3rdCheckAvailableInfo', available: boolean } }; + export type GetSubscriptionsQueryVariables = Exact<{ filters: SubscriptionsFilterInput; orderBy: SubscriptionsOrderInput; @@ -1716,14 +1740,39 @@ export type GetSubscriptionDetailQueryVariables = Exact<{ export type GetSubscriptionDetailQuery = { __typename?: 'Query', subscriptions: { __typename?: 'SubscriptionsConnection', nodes: Array<{ __typename?: 'Subscriptions', id: number, displayName: string, createdAt: string, updatedAt: string, category: SubscriptionCategoryEnum, sourceUrl: string, enabled: boolean, credential3rd?: { __typename?: 'Credential3rd', id: number, username?: string | null } | null, bangumi: { __typename?: 'BangumiConnection', nodes: Array<{ __typename?: 'Bangumi', createdAt: string, updatedAt: string, id: number, mikanBangumiId?: string | null, displayName: string, rawName: string, season: number, seasonRaw?: string | null, fansub?: string | null, mikanFansubId?: string | null, rssLink?: string | null, posterLink?: string | null, savePath?: string | null, homepage?: string | null }> } }> } }; +export type SyncSubscriptionFeedsIncrementalMutationVariables = Exact<{ + id: Scalars['Int']['input']; +}>; + + +export type SyncSubscriptionFeedsIncrementalMutation = { __typename?: 'Mutation', subscriptionSyncOneFeedsIncremental: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } }; + +export type SyncSubscriptionFeedsFullMutationVariables = Exact<{ + id: Scalars['Int']['input']; +}>; + + +export type SyncSubscriptionFeedsFullMutation = { __typename?: 'Mutation', subscriptionSyncOneFeedsFull: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } }; + +export type SyncSubscriptionSourcesMutationVariables = Exact<{ + id: Scalars['Int']['input']; +}>; + + +export type SyncSubscriptionSourcesMutation = { __typename?: 'Mutation', subscriptionSyncOneSources: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } }; + export const GetCredential3rdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCredential3rd"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdFilterInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdOrderInput"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential3rd"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"cookies"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"credentialType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"paginationInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}}]}}]}}]} as unknown as DocumentNode; export const InsertCredential3rdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InsertCredential3rd"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdInsertInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential3rdCreateOne"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"cookies"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"credentialType"}}]}}]}}]} as unknown as DocumentNode; export const UpdateCredential3rdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCredential3rd"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdUpdateInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential3rdUpdate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"cookies"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"credentialType"}}]}}]}}]} as unknown as DocumentNode; export const DeleteCredential3rdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteCredential3rd"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Credential3rdFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"credential3rdDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}}]}]}}]} as unknown as DocumentNode; export const GetCredential3rdDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCredential3rdDetail"},"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":"credential3rd"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"cookies"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}},{"kind":"Field","name":{"kind":"Name","value":"userAgent"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"credentialType"}}]}}]}}]}}]} as unknown as DocumentNode; +export const CheckCredential3rdAvailableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CheckCredential3rdAvailable"},"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":"credential3rdCheckAvailable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"available"}}]}}]}}]} as unknown as DocumentNode; export const GetSubscriptionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSubscriptions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsOrderInput"}}}},{"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":"subscriptions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}},{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"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"}}]}},{"kind":"Field","name":{"kind":"Name","value":"paginationInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}}]}}]}}]} as unknown as DocumentNode; export const 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; export const UpdateSubscriptionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSubscriptions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsUpdateInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsUpdate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}}]}}]}}]} as unknown as DocumentNode; export const DeleteSubscriptionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteSubscriptions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriptionsFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionsDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}}]}]}}]} as unknown as DocumentNode; -export const GetSubscriptionDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSubscriptionDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"credential3rd"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bangumi"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"mikanBangumiId"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"rawName"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"seasonRaw"}},{"kind":"Field","name":{"kind":"Name","value":"fansub"}},{"kind":"Field","name":{"kind":"Name","value":"mikanFansubId"}},{"kind":"Field","name":{"kind":"Name","value":"rssLink"}},{"kind":"Field","name":{"kind":"Name","value":"posterLink"}},{"kind":"Field","name":{"kind":"Name","value":"savePath"}},{"kind":"Field","name":{"kind":"Name","value":"homepage"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const GetSubscriptionDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSubscriptionDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"sourceUrl"}},{"kind":"Field","name":{"kind":"Name","value":"enabled"}},{"kind":"Field","name":{"kind":"Name","value":"credential3rd"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"bangumi"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"mikanBangumiId"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"rawName"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"seasonRaw"}},{"kind":"Field","name":{"kind":"Name","value":"fansub"}},{"kind":"Field","name":{"kind":"Name","value":"mikanFansubId"}},{"kind":"Field","name":{"kind":"Name","value":"rssLink"}},{"kind":"Field","name":{"kind":"Name","value":"posterLink"}},{"kind":"Field","name":{"kind":"Name","value":"savePath"}},{"kind":"Field","name":{"kind":"Name","value":"homepage"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const SyncSubscriptionFeedsIncrementalDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionFeedsIncremental"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionSyncOneFeedsIncremental"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"taskId"}}]}}]}}]} as unknown as DocumentNode; +export const SyncSubscriptionFeedsFullDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionFeedsFull"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionSyncOneFeedsFull"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"taskId"}}]}}]}}]} as unknown as DocumentNode; +export const SyncSubscriptionSourcesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionSources"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionSyncOneSources"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"taskId"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/apps/webui/src/infra/graphql/graphql.service.ts b/apps/webui/src/infra/graphql/graphql.service.ts index db933cf..55e6ca9 100644 --- a/apps/webui/src/infra/graphql/graphql.service.ts +++ b/apps/webui/src/infra/graphql/graphql.service.ts @@ -22,6 +22,17 @@ export class GraphQLService { _apollo = new ApolloClient({ link: this.authLink.concat(this.apiLink), cache: new InMemoryCache(), + defaultOptions: { + watchQuery: { + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'network-only', + errorPolicy: 'all', + }, + query: { + fetchPolicy: 'network-only', + errorPolicy: 'all', + }, + }, }); query = this._apollo.query; diff --git a/apps/webui/src/presentation/routeTree.gen.ts b/apps/webui/src/presentation/routeTree.gen.ts index 81339b8..cd916fd 100644 --- a/apps/webui/src/presentation/routeTree.gen.ts +++ b/apps/webui/src/presentation/routeTree.gen.ts @@ -17,12 +17,14 @@ import { Route as AppRouteImport } from './routes/_app/route' import { Route as IndexImport } from './routes/index' import { Route as AuthSignUpImport } from './routes/auth/sign-up' import { Route as AuthSignInImport } from './routes/auth/sign-in' +import { Route as AppTasksRouteImport } from './routes/_app/tasks/route' import { Route as AppSubscriptionsRouteImport } from './routes/_app/subscriptions/route' import { Route as AppSettingsRouteImport } from './routes/_app/settings/route' import { Route as AppPlaygroundRouteImport } from './routes/_app/playground/route' import { Route as AppCredential3rdRouteImport } from './routes/_app/credential3rd/route' import { Route as AppBangumiRouteImport } from './routes/_app/bangumi/route' import { Route as AuthOidcCallbackImport } from './routes/auth/oidc/callback' +import { Route as AppTasksManageImport } from './routes/_app/tasks/manage' import { Route as AppSubscriptionsManageImport } from './routes/_app/subscriptions/manage' import { Route as AppSubscriptionsCreateImport } from './routes/_app/subscriptions/create' import { Route as AppSettingsDownloaderImport } from './routes/_app/settings/downloader' @@ -32,6 +34,7 @@ import { Route as AppCredential3rdCreateImport } from './routes/_app/credential3 import { Route as AppBangumiManageImport } from './routes/_app/bangumi/manage' import { Route as AppExploreFeedImport } from './routes/_app/_explore/feed' import { Route as AppExploreExploreImport } from './routes/_app/_explore/explore' +import { Route as AppTasksDetailIdImport } from './routes/_app/tasks/detail.$id' import { Route as AppSubscriptionsEditIdImport } from './routes/_app/subscriptions/edit.$id' import { Route as AppSubscriptionsDetailIdImport } from './routes/_app/subscriptions/detail.$id' import { Route as AppCredential3rdEditIdImport } from './routes/_app/credential3rd/edit.$id' @@ -74,6 +77,12 @@ const AuthSignInRoute = AuthSignInImport.update({ getParentRoute: () => rootRoute, } as any) +const AppTasksRouteRoute = AppTasksRouteImport.update({ + id: '/tasks', + path: '/tasks', + getParentRoute: () => AppRouteRoute, +} as any) + const AppSubscriptionsRouteRoute = AppSubscriptionsRouteImport.update({ id: '/subscriptions', path: '/subscriptions', @@ -110,6 +119,12 @@ const AuthOidcCallbackRoute = AuthOidcCallbackImport.update({ getParentRoute: () => rootRoute, } as any) +const AppTasksManageRoute = AppTasksManageImport.update({ + id: '/manage', + path: '/manage', + getParentRoute: () => AppTasksRouteRoute, +} as any) + const AppSubscriptionsManageRoute = AppSubscriptionsManageImport.update({ id: '/manage', path: '/manage', @@ -166,6 +181,12 @@ const AppExploreExploreRoute = AppExploreExploreImport.update({ getParentRoute: () => AppRouteRoute, } as any) +const AppTasksDetailIdRoute = AppTasksDetailIdImport.update({ + id: '/detail/$id', + path: '/detail/$id', + getParentRoute: () => AppTasksRouteRoute, +} as any) + const AppSubscriptionsEditIdRoute = AppSubscriptionsEditIdImport.update({ id: '/edit/$id', path: '/edit/$id', @@ -257,6 +278,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppSubscriptionsRouteImport parentRoute: typeof AppRouteImport } + '/_app/tasks': { + id: '/_app/tasks' + path: '/tasks' + fullPath: '/tasks' + preLoaderRoute: typeof AppTasksRouteImport + parentRoute: typeof AppRouteImport + } '/auth/sign-in': { id: '/auth/sign-in' path: '/auth/sign-in' @@ -334,6 +362,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppSubscriptionsManageImport parentRoute: typeof AppSubscriptionsRouteImport } + '/_app/tasks/manage': { + id: '/_app/tasks/manage' + path: '/manage' + fullPath: '/tasks/manage' + preLoaderRoute: typeof AppTasksManageImport + parentRoute: typeof AppTasksRouteImport + } '/auth/oidc/callback': { id: '/auth/oidc/callback' path: '/auth/oidc/callback' @@ -369,6 +404,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppSubscriptionsEditIdImport parentRoute: typeof AppSubscriptionsRouteImport } + '/_app/tasks/detail/$id': { + id: '/_app/tasks/detail/$id' + path: '/detail/$id' + fullPath: '/tasks/detail/$id' + preLoaderRoute: typeof AppTasksDetailIdImport + parentRoute: typeof AppTasksRouteImport + } } } @@ -446,12 +488,27 @@ const AppSubscriptionsRouteRouteWithChildren = AppSubscriptionsRouteRouteChildren, ) +interface AppTasksRouteRouteChildren { + AppTasksManageRoute: typeof AppTasksManageRoute + AppTasksDetailIdRoute: typeof AppTasksDetailIdRoute +} + +const AppTasksRouteRouteChildren: AppTasksRouteRouteChildren = { + AppTasksManageRoute: AppTasksManageRoute, + AppTasksDetailIdRoute: AppTasksDetailIdRoute, +} + +const AppTasksRouteRouteWithChildren = AppTasksRouteRoute._addFileChildren( + AppTasksRouteRouteChildren, +) + interface AppRouteRouteChildren { AppBangumiRouteRoute: typeof AppBangumiRouteRouteWithChildren AppCredential3rdRouteRoute: typeof AppCredential3rdRouteRouteWithChildren AppPlaygroundRouteRoute: typeof AppPlaygroundRouteRouteWithChildren AppSettingsRouteRoute: typeof AppSettingsRouteRouteWithChildren AppSubscriptionsRouteRoute: typeof AppSubscriptionsRouteRouteWithChildren + AppTasksRouteRoute: typeof AppTasksRouteRouteWithChildren AppExploreExploreRoute: typeof AppExploreExploreRoute AppExploreFeedRoute: typeof AppExploreFeedRoute } @@ -462,6 +519,7 @@ const AppRouteRouteChildren: AppRouteRouteChildren = { AppPlaygroundRouteRoute: AppPlaygroundRouteRouteWithChildren, AppSettingsRouteRoute: AppSettingsRouteRouteWithChildren, AppSubscriptionsRouteRoute: AppSubscriptionsRouteRouteWithChildren, + AppTasksRouteRoute: AppTasksRouteRouteWithChildren, AppExploreExploreRoute: AppExploreExploreRoute, AppExploreFeedRoute: AppExploreFeedRoute, } @@ -480,6 +538,7 @@ export interface FileRoutesByFullPath { '/playground': typeof AppPlaygroundRouteRouteWithChildren '/settings': typeof AppSettingsRouteRouteWithChildren '/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren + '/tasks': typeof AppTasksRouteRouteWithChildren '/auth/sign-in': typeof AuthSignInRoute '/auth/sign-up': typeof AuthSignUpRoute '/explore': typeof AppExploreExploreRoute @@ -491,11 +550,13 @@ export interface FileRoutesByFullPath { '/settings/downloader': typeof AppSettingsDownloaderRoute '/subscriptions/create': typeof AppSubscriptionsCreateRoute '/subscriptions/manage': typeof AppSubscriptionsManageRoute + '/tasks/manage': typeof AppTasksManageRoute '/auth/oidc/callback': typeof AuthOidcCallbackRoute '/credential3rd/detail/$id': typeof AppCredential3rdDetailIdRoute '/credential3rd/edit/$id': typeof AppCredential3rdEditIdRoute '/subscriptions/detail/$id': typeof AppSubscriptionsDetailIdRoute '/subscriptions/edit/$id': typeof AppSubscriptionsEditIdRoute + '/tasks/detail/$id': typeof AppTasksDetailIdRoute } export interface FileRoutesByTo { @@ -508,6 +569,7 @@ export interface FileRoutesByTo { '/playground': typeof AppPlaygroundRouteRouteWithChildren '/settings': typeof AppSettingsRouteRouteWithChildren '/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren + '/tasks': typeof AppTasksRouteRouteWithChildren '/auth/sign-in': typeof AuthSignInRoute '/auth/sign-up': typeof AuthSignUpRoute '/explore': typeof AppExploreExploreRoute @@ -519,11 +581,13 @@ export interface FileRoutesByTo { '/settings/downloader': typeof AppSettingsDownloaderRoute '/subscriptions/create': typeof AppSubscriptionsCreateRoute '/subscriptions/manage': typeof AppSubscriptionsManageRoute + '/tasks/manage': typeof AppTasksManageRoute '/auth/oidc/callback': typeof AuthOidcCallbackRoute '/credential3rd/detail/$id': typeof AppCredential3rdDetailIdRoute '/credential3rd/edit/$id': typeof AppCredential3rdEditIdRoute '/subscriptions/detail/$id': typeof AppSubscriptionsDetailIdRoute '/subscriptions/edit/$id': typeof AppSubscriptionsEditIdRoute + '/tasks/detail/$id': typeof AppTasksDetailIdRoute } export interface FileRoutesById { @@ -537,6 +601,7 @@ export interface FileRoutesById { '/_app/playground': typeof AppPlaygroundRouteRouteWithChildren '/_app/settings': typeof AppSettingsRouteRouteWithChildren '/_app/subscriptions': typeof AppSubscriptionsRouteRouteWithChildren + '/_app/tasks': typeof AppTasksRouteRouteWithChildren '/auth/sign-in': typeof AuthSignInRoute '/auth/sign-up': typeof AuthSignUpRoute '/_app/_explore/explore': typeof AppExploreExploreRoute @@ -548,11 +613,13 @@ export interface FileRoutesById { '/_app/settings/downloader': typeof AppSettingsDownloaderRoute '/_app/subscriptions/create': typeof AppSubscriptionsCreateRoute '/_app/subscriptions/manage': typeof AppSubscriptionsManageRoute + '/_app/tasks/manage': typeof AppTasksManageRoute '/auth/oidc/callback': typeof AuthOidcCallbackRoute '/_app/credential3rd/detail/$id': typeof AppCredential3rdDetailIdRoute '/_app/credential3rd/edit/$id': typeof AppCredential3rdEditIdRoute '/_app/subscriptions/detail/$id': typeof AppSubscriptionsDetailIdRoute '/_app/subscriptions/edit/$id': typeof AppSubscriptionsEditIdRoute + '/_app/tasks/detail/$id': typeof AppTasksDetailIdRoute } export interface FileRouteTypes { @@ -567,6 +634,7 @@ export interface FileRouteTypes { | '/playground' | '/settings' | '/subscriptions' + | '/tasks' | '/auth/sign-in' | '/auth/sign-up' | '/explore' @@ -578,11 +646,13 @@ export interface FileRouteTypes { | '/settings/downloader' | '/subscriptions/create' | '/subscriptions/manage' + | '/tasks/manage' | '/auth/oidc/callback' | '/credential3rd/detail/$id' | '/credential3rd/edit/$id' | '/subscriptions/detail/$id' | '/subscriptions/edit/$id' + | '/tasks/detail/$id' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -594,6 +664,7 @@ export interface FileRouteTypes { | '/playground' | '/settings' | '/subscriptions' + | '/tasks' | '/auth/sign-in' | '/auth/sign-up' | '/explore' @@ -605,11 +676,13 @@ export interface FileRouteTypes { | '/settings/downloader' | '/subscriptions/create' | '/subscriptions/manage' + | '/tasks/manage' | '/auth/oidc/callback' | '/credential3rd/detail/$id' | '/credential3rd/edit/$id' | '/subscriptions/detail/$id' | '/subscriptions/edit/$id' + | '/tasks/detail/$id' id: | '__root__' | '/' @@ -621,6 +694,7 @@ export interface FileRouteTypes { | '/_app/playground' | '/_app/settings' | '/_app/subscriptions' + | '/_app/tasks' | '/auth/sign-in' | '/auth/sign-up' | '/_app/_explore/explore' @@ -632,11 +706,13 @@ export interface FileRouteTypes { | '/_app/settings/downloader' | '/_app/subscriptions/create' | '/_app/subscriptions/manage' + | '/_app/tasks/manage' | '/auth/oidc/callback' | '/_app/credential3rd/detail/$id' | '/_app/credential3rd/edit/$id' | '/_app/subscriptions/detail/$id' | '/_app/subscriptions/edit/$id' + | '/_app/tasks/detail/$id' fileRoutesById: FileRoutesById } @@ -690,6 +766,7 @@ export const routeTree = rootRoute "/_app/playground", "/_app/settings", "/_app/subscriptions", + "/_app/tasks", "/_app/_explore/explore", "/_app/_explore/feed" ] @@ -741,6 +818,14 @@ export const routeTree = rootRoute "/_app/subscriptions/edit/$id" ] }, + "/_app/tasks": { + "filePath": "_app/tasks/route.tsx", + "parent": "/_app", + "children": [ + "/_app/tasks/manage", + "/_app/tasks/detail/$id" + ] + }, "/auth/sign-in": { "filePath": "auth/sign-in.tsx" }, @@ -783,6 +868,10 @@ export const routeTree = rootRoute "filePath": "_app/subscriptions/manage.tsx", "parent": "/_app/subscriptions" }, + "/_app/tasks/manage": { + "filePath": "_app/tasks/manage.tsx", + "parent": "/_app/tasks" + }, "/auth/oidc/callback": { "filePath": "auth/oidc/callback.tsx" }, @@ -801,6 +890,10 @@ export const routeTree = rootRoute "/_app/subscriptions/edit/$id": { "filePath": "_app/subscriptions/edit.$id.tsx", "parent": "/_app/subscriptions" + }, + "/_app/tasks/detail/$id": { + "filePath": "_app/tasks/detail.$id.tsx", + "parent": "/_app/tasks" } } } diff --git a/apps/webui/src/presentation/routes/_app/credential3rd/-check-available.tsx b/apps/webui/src/presentation/routes/_app/credential3rd/-check-available.tsx new file mode 100644 index 0000000..103b7e2 --- /dev/null +++ b/apps/webui/src/presentation/routes/_app/credential3rd/-check-available.tsx @@ -0,0 +1,92 @@ +import { Button } from '@/components/ui/button'; +import { + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { CHECK_CREDENTIAL_3RD_AVAILABLE } from '@/domains/recorder/schema/credential3rd'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; +import type { CheckCredential3rdAvailableQuery } from '@/infra/graphql/gql/graphql'; +import { useLazyQuery } from '@apollo/client'; +import { CheckIcon, Loader2, XIcon } from 'lucide-react'; +import { memo, useCallback } from 'react'; +import { toast } from 'sonner'; + +export interface Credential3rdCheckAvailableViewProps { + id: number; +} + +export const Credential3rdCheckAvailableView = memo( + ({ id }: Credential3rdCheckAvailableViewProps) => { + const [checkAvailable, { data, error, loading }] = + useLazyQuery( + CHECK_CREDENTIAL_3RD_AVAILABLE, + { + variables: { id }, + } + ); + + const handleCheckAvailable = useCallback(async () => { + const checkResult = await checkAvailable(); + const error = getApolloQueryError(checkResult); + console.error('error', error); + if (error) { + toast.error('Failed to check available', { + description: apolloErrorToMessage(error), + }); + return; + } + if (checkResult.data?.credential3rdCheckAvailable.available) { + toast.success('Credential is available'); + } else { + toast.error('Credential is not available'); + } + }, [checkAvailable]); + + const available = data?.credential3rdCheckAvailable?.available; + + return ( +
+ +
+ ); + } +); + +export interface Credential3rdCheckAvailableViewDialogContentProps { + id: number; +} + +export const Credential3rdCheckAvailableViewDialogContent = memo( + ({ id }: Credential3rdCheckAvailableViewDialogContentProps) => { + return ( + + + Check Available + + Check if the credential is available. + + + + + ); + } +); diff --git a/apps/webui/src/presentation/routes/_app/credential3rd/detail.$id.tsx b/apps/webui/src/presentation/routes/_app/credential3rd/detail.$id.tsx index 6b1e644..e3fa6ff 100644 --- a/apps/webui/src/presentation/routes/_app/credential3rd/detail.$id.tsx +++ b/apps/webui/src/presentation/routes/_app/credential3rd/detail.$id.tsx @@ -9,6 +9,7 @@ import { CardTitle, } from '@/components/ui/card'; import { DetailEmptyView } from '@/components/ui/detail-empty-view'; +import { Dialog, DialogTrigger } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { QueryErrorView } from '@/components/ui/query-error-view'; import { Separator } from '@/components/ui/separator'; @@ -23,8 +24,9 @@ import { useRouter, } from '@tanstack/react-router'; import { format } from 'date-fns/format'; -import { ArrowLeft, Edit, Eye, EyeOff } from 'lucide-react'; +import { ArrowLeft, CheckIcon, Edit, Eye, EyeOff } from 'lucide-react'; import { useState } from 'react'; +import { Credential3rdCheckAvailableViewDialogContent } from './-check-available'; export const Route = createFileRoute('/_app/credential3rd/detail/$id')({ component: Credential3rdDetailRouteComponent, @@ -57,7 +59,6 @@ function Credential3rdDetailRouteComponent() { variables: { id: Number.parseInt(id), }, - fetchPolicy: 'cache-and-network', } ); @@ -125,7 +126,19 @@ function Credential3rdDetailRouteComponent() { View credential detail - {credential.credentialType} +
+ + + + + + +
diff --git a/apps/webui/src/presentation/routes/_app/credential3rd/edit.$id.tsx b/apps/webui/src/presentation/routes/_app/credential3rd/edit.$id.tsx index 72a5515..85d2665 100644 --- a/apps/webui/src/presentation/routes/_app/credential3rd/edit.$id.tsx +++ b/apps/webui/src/presentation/routes/_app/credential3rd/edit.$id.tsx @@ -27,6 +27,10 @@ import { GET_CREDENTIAL_3RD_DETAIL, UPDATE_CREDENTIAL_3RD, } from '@/domains/recorder/schema/credential3rd'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; import type { Credential3rdTypeEnum, GetCredential3rdDetailQuery, @@ -260,19 +264,19 @@ function Credential3rdEditRouteComponent() { variables: { id: Number.parseInt(id), }, - fetchPolicy: 'cache-and-network', }); const credential = data?.credential3rd?.nodes?.[0]; const onCompleted = useCallback(async () => { const refetchResult = await refetch(); - if (refetchResult.errors) { - toast('Update credential failed', { - description: refetchResult.errors[0].message, + const error = getApolloQueryError(refetchResult); + if (error) { + toast.error('Update credential failed', { + description: apolloErrorToMessage(error), }); } else { - toast('Update credential successfully'); + toast.success('Update credential successfully'); } }, [refetch]); diff --git a/apps/webui/src/presentation/routes/_app/credential3rd/manage.tsx b/apps/webui/src/presentation/routes/_app/credential3rd/manage.tsx index bec79ef..f6c8718 100644 --- a/apps/webui/src/presentation/routes/_app/credential3rd/manage.tsx +++ b/apps/webui/src/presentation/routes/_app/credential3rd/manage.tsx @@ -18,6 +18,10 @@ import { DELETE_CREDENTIAL_3RD, GET_CREDENTIAL_3RD, } from '@/domains/recorder/schema/credential3rd'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; import type { GetCredential3rdQuery } from '@/infra/graphql/gql/graphql'; import type { RouteStateDataOption } from '@/infra/routes/traits'; import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton'; @@ -79,17 +83,16 @@ function CredentialManageRouteComponent() { }, }, }, - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'network-only', } ); const [deleteCredential] = useMutation(DELETE_CREDENTIAL_3RD, { onCompleted: async () => { const refetchResult = await refetch(); - if (refetchResult.errors) { + const error = getApolloQueryError(refetchResult); + if (error) { toast.error('Failed to delete credential', { - description: refetchResult.errors[0].message, + description: apolloErrorToMessage(error), }); return; } diff --git a/apps/webui/src/presentation/routes/_app/subscriptions/-credential3rd-select.tsx b/apps/webui/src/presentation/routes/_app/subscriptions/-credential3rd-select.tsx index fdce79c..6bc3af8 100644 --- a/apps/webui/src/presentation/routes/_app/subscriptions/-credential3rd-select.tsx +++ b/apps/webui/src/presentation/routes/_app/subscriptions/-credential3rd-select.tsx @@ -28,8 +28,6 @@ export function Credential3rdSelectContent({ GetCredential3rdQuery, GetCredential3rdQueryVariables >(GET_CREDENTIAL_3RD, { - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-and-network', variables: { filters: { credentialType: { diff --git a/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx b/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx new file mode 100644 index 0000000..9e4909b --- /dev/null +++ b/apps/webui/src/presentation/routes/_app/subscriptions/-sync.tsx @@ -0,0 +1,160 @@ +import { Button } from '@/components/ui/button'; +import { + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Spinner } from '@/components/ui/spinner'; +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, +} from '@/infra/graphql/gql/graphql'; +import { useMutation } from '@apollo/client'; +import { useNavigate } from '@tanstack/react-router'; +import { RefreshCcwIcon } from 'lucide-react'; +import { memo, useCallback } from 'react'; +import { toast } from 'sonner'; + +export type SubscriptionSyncViewCompletePayload = { + taskId: string; +}; + +export interface SubscriptionSyncViewProps { + id: number; + onComplete: (payload: SubscriptionSyncViewCompletePayload) => void; +} + +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.subscriptionSyncOneFeedsIncremental); + }, + onError: (error) => { + toast.error('Failed to sync subscription', { + description: error.message, + }); + }, + }); + + const [syncSubscriptionFeedsFull, { loading: loadingFull }] = useMutation< + SyncSubscriptionFeedsFullMutation, + SyncSubscriptionFeedsFullMutationVariables + >(SYNC_SUBSCRIPTION_FEEDS_FULL, { + onCompleted: (data) => { + toast.success('Sync completed'); + onComplete(data.subscriptionSyncOneFeedsFull); + }, + onError: (error) => { + toast.error('Failed to sync subscription', { + description: error.message, + }); + }, + }); + + const [syncSubscriptionSources, { loading: loadingSources }] = useMutation< + SyncSubscriptionSourcesMutation, + SyncSubscriptionSourcesMutationVariables + >(SYNC_SUBSCRIPTION_SOURCES, { + onCompleted: (data) => { + toast.success('Sync completed'); + onComplete(data.subscriptionSyncOneSources); + }, + onError: (error) => { + toast.error('Failed to sync subscription', { + description: error.message, + }); + }, + }); + + const loading = loadingIncremental || loadingFull || loadingSources; + + return ( +
+ + + + + {loading && ( +
+ + Syncing... +
+ )} +
+ ); + } +); + +export interface SubscriptionSyncDialogContentProps { + id: number; + onCancel: VoidFunction; +} + +export const SubscriptionSyncDialogContent = memo( + ({ id, onCancel }: SubscriptionSyncDialogContentProps) => { + const navigate = useNavigate(); + + const handleSyncComplete = useCallback( + (payload: SubscriptionSyncViewCompletePayload) => { + navigate({ + to: '/tasks/detail/$id', + params: { + id: `${payload.taskId}`, + }, + }); + }, + [navigate] + ); + + return ( + + + Sync Subscription + + Sync the subscription with sources and feeds. + + + + + ); + } +); diff --git a/apps/webui/src/presentation/routes/_app/subscriptions/detail.$id.tsx b/apps/webui/src/presentation/routes/_app/subscriptions/detail.$id.tsx index d857174..3883dff 100644 --- a/apps/webui/src/presentation/routes/_app/subscriptions/detail.$id.tsx +++ b/apps/webui/src/presentation/routes/_app/subscriptions/detail.$id.tsx @@ -9,12 +9,17 @@ import { CardTitle, } from '@/components/ui/card'; import { DetailEmptyView } from '@/components/ui/detail-empty-view'; +import { Dialog, DialogTrigger } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { QueryErrorView } from '@/components/ui/query-error-view'; import { Separator } from '@/components/ui/separator'; import { GET_SUBSCRIPTION_DETAIL } from '@/domains/recorder/schema/subscriptions'; import { SubscriptionService } from '@/domains/recorder/services/subscription.service'; import { useInject } from '@/infra/di/inject'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; import { type GetSubscriptionDetailQuery, SubscriptionCategoryEnum, @@ -27,8 +32,16 @@ import { useRouter, } from '@tanstack/react-router'; import { format } from 'date-fns'; -import { ArrowLeft, Edit, ExternalLink } from 'lucide-react'; +import { + ArrowLeft, + Edit, + ExternalLink, + ListIcon, + RefreshCcwIcon, +} from 'lucide-react'; import { useMemo } from 'react'; +import { toast } from 'sonner'; +import { SubscriptionSyncDialogContent } from './-sync'; export const Route = createFileRoute('/_app/subscriptions/detail/$id')({ component: SubscriptionDetailRouteComponent, @@ -51,15 +64,22 @@ function SubscriptionDetailRouteComponent() { } }; - const { data, loading, error } = useQuery( - GET_SUBSCRIPTION_DETAIL, - { + const handleReload = async () => { + const result = await refetch(); + const error = getApolloQueryError(result); + if (error) { + toast.error('Failed to reload subscription', { + description: apolloErrorToMessage(error), + }); + } + }; + + const { data, loading, error, refetch } = + useQuery(GET_SUBSCRIPTION_DETAIL, { variables: { id: Number.parseInt(id), }, - fetchPolicy: 'cache-and-network', - } - ); + }); const handleEnterEditMode = () => { navigate({ @@ -69,6 +89,7 @@ function SubscriptionDetailRouteComponent() { }, }); }; + const subscription = data?.subscriptions?.nodes?.[0]; const sourceUrlMeta = useMemo( @@ -123,7 +144,7 @@ function SubscriptionDetailRouteComponent() { + {' '} @@ -137,10 +158,30 @@ function SubscriptionDetailRouteComponent() {
- - {subscription.enabled ? 'Enabled' : 'Disabled'} - - {subscription.category} + + + + + + +
@@ -173,11 +214,9 @@ function SubscriptionDetailRouteComponent() {
- + {subscription.enabled ? 'Enabled' : 'Disabled'} - +
diff --git a/apps/webui/src/presentation/routes/_app/subscriptions/edit.$id.tsx b/apps/webui/src/presentation/routes/_app/subscriptions/edit.$id.tsx index 4a0832c..9bb9426 100644 --- a/apps/webui/src/presentation/routes/_app/subscriptions/edit.$id.tsx +++ b/apps/webui/src/presentation/routes/_app/subscriptions/edit.$id.tsx @@ -31,6 +31,10 @@ import { } from '@/domains/recorder/schema/subscriptions'; import { SubscriptionService } from '@/domains/recorder/services/subscription.service'; import { useInject } from '@/infra/di/inject'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; import { Credential3rdTypeEnum, type GetSubscriptionDetailQuery, @@ -382,16 +386,16 @@ function SubscriptionEditRouteComponent() { variables: { id: Number.parseInt(id), }, - fetchPolicy: 'cache-and-network', }); const subscription = data?.subscriptions?.nodes?.[0]; const onCompleted = useCallback(async () => { const refetchResult = await refetch(); - if (refetchResult.errors) { + const error = getApolloQueryError(refetchResult); + if (error) { toast.error('Update subscription failed', { - description: refetchResult.errors[0].message, + description: apolloErrorToMessage(error), }); } else { toast.success('Update subscription successfully'); diff --git a/apps/webui/src/presentation/routes/_app/subscriptions/manage.tsx b/apps/webui/src/presentation/routes/_app/subscriptions/manage.tsx index c2985bf..140b955 100644 --- a/apps/webui/src/presentation/routes/_app/subscriptions/manage.tsx +++ b/apps/webui/src/presentation/routes/_app/subscriptions/manage.tsx @@ -19,6 +19,10 @@ import { type SubscriptionDto, UPDATE_SUBSCRIPTIONS, } from '@/domains/recorder/schema/subscriptions'; +import { + apolloErrorToMessage, + getApolloQueryError, +} from '@/infra/errors/apollo'; import type { GetSubscriptionsQuery, SubscriptionsUpdateInput, @@ -81,15 +85,16 @@ function SubscriptionManageRouteComponent() { updatedAt: 'DESC', }, }, - refetchWritePolicy: 'overwrite', - nextFetchPolicy: 'network-only', } ); const [updateSubscription] = useMutation(UPDATE_SUBSCRIPTIONS, { onCompleted: async () => { const refetchResult = await refetch(); - if (refetchResult.errors) { - toast.error(refetchResult.errors[0].message); + const error = getApolloQueryError(refetchResult); + if (error) { + toast.error('Failed to update subscription', { + description: apolloErrorToMessage(error), + }); return; } toast.success('Subscription updated'); @@ -103,8 +108,11 @@ function SubscriptionManageRouteComponent() { const [deleteSubscription] = useMutation(DELETE_SUBSCRIPTIONS, { onCompleted: async () => { const refetchResult = await refetch(); - if (refetchResult.errors) { - toast.error(refetchResult.errors[0].message); + const error = getApolloQueryError(refetchResult); + if (error) { + toast.error('Failed to delete subscription', { + description: apolloErrorToMessage(error), + }); return; } toast.success('Subscription deleted'); diff --git a/apps/webui/src/presentation/routes/_app/tasks/detail.$id.tsx b/apps/webui/src/presentation/routes/_app/tasks/detail.$id.tsx new file mode 100644 index 0000000..d6c4619 --- /dev/null +++ b/apps/webui/src/presentation/routes/_app/tasks/detail.$id.tsx @@ -0,0 +1,13 @@ +import type { RouteStateDataOption } from '@/infra/routes/traits'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/_app/tasks/detail/$id')({ + component: TaskDetailRouteComponent, + staticData: { + breadcrumb: { label: 'Detail' }, + } satisfies RouteStateDataOption, +}); + +function TaskDetailRouteComponent() { + return
Hello "/_app/tasks/detail/$id"!
; +} diff --git a/apps/webui/src/presentation/routes/_app/tasks/manage.tsx b/apps/webui/src/presentation/routes/_app/tasks/manage.tsx new file mode 100644 index 0000000..8fe8e40 --- /dev/null +++ b/apps/webui/src/presentation/routes/_app/tasks/manage.tsx @@ -0,0 +1,13 @@ +import type { RouteStateDataOption } from '@/infra/routes/traits'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/_app/tasks/manage')({ + component: TaskManageRouteComponent, + staticData: { + breadcrumb: { label: 'Manage' }, + } satisfies RouteStateDataOption, +}); + +function TaskManageRouteComponent() { + return
Hello "/_app/tasks/manage"!
; +} diff --git a/apps/webui/src/presentation/routes/_app/tasks/route.tsx b/apps/webui/src/presentation/routes/_app/tasks/route.tsx new file mode 100644 index 0000000..e7c5e04 --- /dev/null +++ b/apps/webui/src/presentation/routes/_app/tasks/route.tsx @@ -0,0 +1,8 @@ +import { buildVirtualBranchRouteOptions } from '@/infra/routes/utils'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/_app/tasks')( + buildVirtualBranchRouteOptions({ + title: 'Tasks', + }) +); diff --git a/justfile b/justfile index 040c699..6dc1d5d 100644 --- a/justfile +++ b/justfile @@ -1,8 +1,8 @@ set windows-shell := ["pwsh.exe", "-c"] set dotenv-load := true -prepare-dev-recorder: - cargo install sea-orm-cli cargo-llvm-cov cargo-nextest +prepare-dev: + cargo install sea-orm-cli cargo-llvm-cov cargo-nextest killport # install watchexec prepare-dev-testcontainers: @@ -14,10 +14,11 @@ dev-webui: pnpm run --filter=webui dev dev-proxy: + npx kill-port 8899 pnpm run --filter=proxy dev dev-recorder: - watchexec -r -w apps/recorder -- cargo run -p recorder --bin recorder_cli -- --environment development + watchexec -r -e rs,toml,yaml,json,env -- cargo run -p recorder --bin recorder_cli -- --environment development dev-recorder-migrate-down: cargo run -p recorder --bin migrate_down -- --environment development diff --git a/package.json b/package.json index c33d874..810cae1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Kono bangumi?", "license": "MIT", "workspaces": ["packages/*", "apps/*"], + "type": "module", "repository": { "type": "git", "url": "https://github.com/dumtruck/konobangu.git" @@ -28,10 +29,12 @@ "@auto-it/all-contributors": "^11.3.0", "@auto-it/first-time-contributor": "^11.3.0", "@biomejs/biome": "1.9.4", - "@types/node": "^22.15.29", + "@types/node": "^24.0.0", "tsx": "^4.19.4", "turbo": "^2.5.4", "typescript": "^5.8.3", - "ultracite": "^4.2.8" + "ultracite": "^4.2.8", + "kill-port": "^2.0.1", + "cross-env": "^7.0.3" } } diff --git a/packages/downloader/src/bittorrent/source.rs b/packages/downloader/src/bittorrent/source.rs index 7d778e8..48fad23 100644 --- a/packages/downloader/src/bittorrent/source.rs +++ b/packages/downloader/src/bittorrent/source.rs @@ -4,7 +4,7 @@ use std::{ }; use bytes::Bytes; -use fetch::{bytes::fetch_bytes, client::core::HttpClientTrait}; +use fetch::{bytes::fetch_bytes, client::HttpClientTrait}; use librqbit_core::{magnet::Magnet, torrent_metainfo, torrent_metainfo::TorrentMetaV1Owned}; use snafu::ResultExt; use url::Url; diff --git a/packages/fetch/Cargo.toml b/packages/fetch/Cargo.toml index 5eb1ccc..4cafdbd 100644 --- a/packages/fetch/Cargo.toml +++ b/packages/fetch/Cargo.toml @@ -16,6 +16,7 @@ axum-extra = { workspace = true } async-trait = { workspace = true } moka = { workspace = true } reqwest = { workspace = true } +tracing = { workspace = true } leaky-bucket = "1.1" http-cache-reqwest = { version = "0.15", features = [ "manager-cacache", @@ -32,5 +33,6 @@ http-cache = { version = "0.20", features = [ "manager-moka", ], default-features = false } reqwest_cookie_store = { version = "0.8.0", features = ["serde"] } +http-serde = "2.1.1" util = { workspace = true } diff --git a/packages/fetch/src/client/core.rs b/packages/fetch/src/client/core.rs index 9e7d360..8b24052 100644 --- a/packages/fetch/src/client/core.rs +++ b/packages/fetch/src/client/core.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, ops::Deref, sync::Arc, time::Duration}; use async_trait::async_trait; -use axum::http::{self, Extensions}; +use axum::http::Extensions; use http_cache_reqwest::{ Cache, CacheManager, CacheMode, HttpCache, HttpCacheOptions, MokaManager, }; @@ -15,10 +15,9 @@ use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; use reqwest_tracing::TracingMiddleware; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -use snafu::Snafu; use util::OptDynErr; -use crate::get_random_ua; +use crate::{HttpClientError, client::proxy::HttpClientProxyConfig, get_random_ua}; pub struct RateLimiterMiddleware { rate_limiter: RateLimiter, @@ -56,7 +55,7 @@ impl Default for HttpClientCachePresetConfig { } #[serde_as] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct HttpClientConfig { pub exponential_backoff_max_retries: Option, pub leaky_bucket_max_tokens: Option, @@ -67,6 +66,7 @@ pub struct HttpClientConfig { pub user_agent: Option, pub cache_backend: Option, pub cache_preset: Option, + pub proxy: Option, } pub(crate) struct CacheBackend(Box); @@ -102,20 +102,6 @@ impl CacheManager for CacheBackend { } } -#[derive(Debug, Snafu)] -pub enum HttpClientError { - #[snafu(transparent)] - ReqwestError { source: reqwest::Error }, - #[snafu(transparent)] - ReqwestMiddlewareError { source: reqwest_middleware::Error }, - #[snafu(transparent)] - HttpError { source: http::Error }, - #[snafu(display("Failed to parse cookies: {}", source))] - ParseCookiesError { source: serde_json::Error }, - #[snafu(display("Failed to save cookies, message: {}, source: {:?}", message, source))] - SaveCookiesError { message: String, source: OptDynErr }, -} - pub trait HttpClientTrait: Deref + Debug {} pub struct HttpClientFork { @@ -179,13 +165,29 @@ impl Deref for HttpClient { impl HttpClient { pub fn from_config(config: HttpClientConfig) -> Result { let mut middleware_stack: Vec> = vec![]; - let reqwest_client_builder = ClientBuilder::new().user_agent( + let mut reqwest_client_builder = ClientBuilder::new().user_agent( config .user_agent .as_deref() .unwrap_or_else(|| get_random_ua()), ); + if let Some(proxy) = config.proxy.as_ref() { + let accept_invalid_certs = proxy + .accept_invalid_certs + .as_ref() + .map(|b| b.as_bool()) + .unwrap_or_default(); + let proxy = proxy.clone().into_proxy()?; + if let Some(proxy) = proxy { + reqwest_client_builder = reqwest_client_builder.proxy(proxy); + if accept_invalid_certs { + reqwest_client_builder = + reqwest_client_builder.danger_accept_invalid_certs(true); + } + } + } + #[cfg(not(target_arch = "wasm32"))] let reqwest_client_builder = reqwest_client_builder.redirect(reqwest::redirect::Policy::none()); @@ -294,13 +296,29 @@ impl HttpClient { } pub fn fork(&self) -> HttpClientFork { - let reqwest_client_builder = ClientBuilder::new().user_agent( + let mut reqwest_client_builder = ClientBuilder::new().user_agent( self.config .user_agent .as_deref() .unwrap_or_else(|| get_random_ua()), ); + if let Some(proxy) = self.config.proxy.as_ref() { + let accept_invalid_certs = proxy + .accept_invalid_certs + .as_ref() + .map(|b| b.as_bool()) + .unwrap_or_default(); + let proxy = proxy.clone().into_proxy().unwrap_or_default(); + if let Some(proxy) = proxy { + reqwest_client_builder = reqwest_client_builder.proxy(proxy); + if accept_invalid_certs { + reqwest_client_builder = + reqwest_client_builder.danger_accept_invalid_certs(true); + } + } + } + #[cfg(not(target_arch = "wasm32"))] let reqwest_client_builder = reqwest_client_builder.redirect(reqwest::redirect::Policy::none()); diff --git a/packages/fetch/src/client/error.rs b/packages/fetch/src/client/error.rs new file mode 100644 index 0000000..fccb61e --- /dev/null +++ b/packages/fetch/src/client/error.rs @@ -0,0 +1,21 @@ +use axum::http; +use snafu::Snafu; +use util::OptDynErr; + +#[derive(Debug, Snafu)] +pub enum HttpClientError { + #[snafu(transparent)] + ReqwestError { source: reqwest::Error }, + #[snafu(transparent)] + ReqwestMiddlewareError { source: reqwest_middleware::Error }, + #[snafu(transparent)] + HttpError { source: http::Error }, + #[snafu(display("Failed to parse cookies: {}", source))] + ParseCookiesError { source: serde_json::Error }, + #[snafu(display("Failed to save cookies, message: {}, source: {:?}", message, source))] + SaveCookiesError { message: String, source: OptDynErr }, + #[snafu(display("Failed to parse fetch client proxy: {source}"))] + ProxyParseError { source: reqwest::Error }, + #[snafu(display("Failed to parse fetch client proxy auth header"))] + ProxyAuthHeaderParseError, +} diff --git a/packages/fetch/src/client/mod.rs b/packages/fetch/src/client/mod.rs index 636fe9a..a4ea7b9 100644 --- a/packages/fetch/src/client/mod.rs +++ b/packages/fetch/src/client/mod.rs @@ -1,6 +1,11 @@ -pub mod core; +mod core; +mod error; +mod proxy; pub use core::{ HttpClient, HttpClientCacheBackendConfig, HttpClientCachePresetConfig, HttpClientConfig, - HttpClientError, HttpClientTrait, + HttpClientTrait, }; + +pub use error::HttpClientError; +pub use proxy::HttpClientProxyConfig; diff --git a/packages/fetch/src/client/proxy.rs b/packages/fetch/src/client/proxy.rs new file mode 100644 index 0000000..1730448 --- /dev/null +++ b/packages/fetch/src/client/proxy.rs @@ -0,0 +1,56 @@ +use axum::http::{HeaderMap, HeaderValue}; +use reqwest::{NoProxy, Proxy}; +use serde::{Deserialize, Serialize}; +use serde_with::{NoneAsEmptyString, serde_as}; +use util::BooleanLike; + +use crate::HttpClientError; + +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HttpClientProxyConfig { + #[serde_as(as = "NoneAsEmptyString")] + pub server: Option, + #[serde_as(as = "NoneAsEmptyString")] + pub auth_header: Option, + #[serde(with = "http_serde::option::header_map")] + pub headers: Option, + #[serde_as(as = "NoneAsEmptyString")] + pub no_proxy: Option, + pub accept_invalid_certs: Option, +} + +impl HttpClientProxyConfig { + pub fn into_proxy(self) -> Result, HttpClientError> { + if let Some(server) = self.server { + let mut proxy = Proxy::all(server) + .map_err(|err| HttpClientError::ProxyParseError { source: err })?; + + if let Some(auth_header) = self.auth_header { + let auth_header = HeaderValue::from_str(&auth_header) + .map_err(|_| HttpClientError::ProxyAuthHeaderParseError)?; + proxy = proxy.custom_http_auth(auth_header); + } + + if let Some(headers) = self.headers { + proxy = proxy.headers(headers); + } + + if let Some(no_proxy) = self.no_proxy { + proxy = proxy.no_proxy(NoProxy::from_string(&no_proxy)); + } + + Ok(Some(proxy)) + } else { + Ok(None) + } + } +} + +impl TryFrom for Option { + type Error = HttpClientError; + + fn try_from(value: HttpClientProxyConfig) -> Result, Self::Error> { + value.into_proxy() + } +} diff --git a/packages/util/Cargo.toml b/packages/util/Cargo.toml index 2d3e909..6e13e51 100644 --- a/packages/util/Cargo.toml +++ b/packages/util/Cargo.toml @@ -9,3 +9,5 @@ quirks_path = { workspace = true } snafu = { workspace = true } anyhow = { workspace = true } bytes = { workspace = true } +serde = { workspace = true } +serde_with = { workspace = true } diff --git a/packages/util/src/lib.rs b/packages/util/src/lib.rs index 0e466ec..689510d 100644 --- a/packages/util/src/lib.rs +++ b/packages/util/src/lib.rs @@ -1,3 +1,5 @@ pub mod errors; +pub mod loose; pub use errors::OptDynErr; +pub use loose::BooleanLike; diff --git a/packages/util/src/loose.rs b/packages/util/src/loose.rs new file mode 100644 index 0000000..be33123 --- /dev/null +++ b/packages/util/src/loose.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BooleanLike { + Boolean(bool), + String(String), + Number(i32), +} + +impl BooleanLike { + pub fn as_bool(&self) -> bool { + match self { + BooleanLike::Boolean(b) => *b, + BooleanLike::String(s) => s.to_lowercase() == "true", + BooleanLike::Number(n) => *n != 0, + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 776ea88..9d990ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,16 +10,22 @@ importers: devDependencies: '@auto-it/all-contributors': specifier: ^11.3.0 - version: 11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3) + version: 11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3) '@auto-it/first-time-contributor': specifier: ^11.3.0 - version: 11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3) + version: 11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3) '@biomejs/biome': specifier: 1.9.4 version: 1.9.4 '@types/node': - specifier: ^22.15.29 - version: 22.15.29 + specifier: ^24.0.0 + version: 24.0.0 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + kill-port: + specifier: ^2.0.1 + version: 2.0.1 tsx: specifier: ^4.19.4 version: 4.19.4 @@ -53,9 +59,6 @@ importers: apps/proxy: devDependencies: - cross-env: - specifier: ^7.0.3 - version: 7.0.3 whistle: specifier: ^2.9.93 version: 2.9.99 @@ -82,7 +85,7 @@ importers: version: 0.2.5(solid-js@1.9.7) '@graphiql/toolkit': specifier: ^0.11.1 - version: 0.11.3(@types/node@22.15.29)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0) + version: 0.11.3(@types/node@24.0.0)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0) '@hookform/resolvers': specifier: ^5.0.1 version: 5.0.1(react-hook-form@7.57.0(react@19.1.0)) @@ -211,7 +214,7 @@ importers: version: 8.6.0(react@19.1.0) graphiql: specifier: ^4.0.2 - version: 4.1.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + version: 4.1.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) graphql: specifier: ^16.11.0 version: 16.11.0 @@ -272,7 +275,7 @@ importers: devDependencies: '@graphql-codegen/cli': specifier: ^5.0.6 - version: 5.0.6(@parcel/watcher@2.5.1)(@types/node@22.15.29)(bufferutil@4.0.9)(encoding@0.1.13)(enquirer@2.4.1)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5) + version: 5.0.6(@parcel/watcher@2.5.1)(@types/node@24.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(enquirer@2.4.1)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5) '@graphql-codegen/client-preset': specifier: ^4.8.1 version: 4.8.1(encoding@0.1.13)(graphql@16.11.0) @@ -293,7 +296,7 @@ importers: version: 4.1.8 '@tanstack/router-plugin': specifier: ^1.112.13 - version: 1.120.15(@rsbuild/core@1.3.22)(@tanstack/react-router@1.120.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))(webpack@5.97.1) + version: 1.120.15(@rsbuild/core@1.3.22)(@tanstack/react-router@1.120.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))(webpack@5.97.1) '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -370,10 +373,10 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.5.1(vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0)) + version: 4.5.1(vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0)) vitest: specifier: ^2.1.8 - version: 2.1.9(@types/node@22.15.29)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@6.0.5))(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + version: 2.1.9(@types/node@24.0.0)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@6.0.5))(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) packages: @@ -3527,8 +3530,8 @@ packages: '@types/node@22.10.1': resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} - '@types/node@22.15.29': - resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} + '@types/node@24.0.0': + resolution: {integrity: sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -4804,6 +4807,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-them-args@1.3.2: + resolution: {integrity: sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==} + get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} @@ -5339,6 +5345,10 @@ packages: jsonfile@2.4.0: resolution: {integrity: sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==} + kill-port@2.0.1: + resolution: {integrity: sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ==} + hasBin: true + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -6425,6 +6435,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-exec@1.0.2: + resolution: {integrity: sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==} + shell-quote@1.8.2: resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} engines: {node: '>= 0.4'} @@ -6931,8 +6944,8 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} universal-user-agent@6.0.1: resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} @@ -7398,10 +7411,10 @@ snapshots: lru-cache: 10.4.3 optional: true - '@auto-it/all-contributors@11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3)': + '@auto-it/all-contributors@11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3)': dependencies: '@auto-it/bot-list': 11.3.0 - '@auto-it/core': 11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3) + '@auto-it/core': 11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3) '@octokit/rest': 18.12.0(encoding@0.1.13) all-contributors-cli: 6.19.0(encoding@0.1.13) anymatch: 3.1.3 @@ -7422,7 +7435,7 @@ snapshots: '@auto-it/bot-list@11.3.0': {} - '@auto-it/core@11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3)': + '@auto-it/core@11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3)': dependencies: '@auto-it/bot-list': 11.3.0 '@endemolshinegroup/cosmiconfig-typescript-loader': 3.0.2(cosmiconfig@7.0.0)(typescript@5.8.3) @@ -7459,24 +7472,24 @@ snapshots: tapable: 2.2.2 terminal-link: 2.1.1 tinycolor2: 1.6.0 - ts-node: 10.9.2(@types/node@22.15.29)(typescript@5.8.3) + ts-node: 10.9.2(@types/node@24.0.0)(typescript@5.8.3) tslib: 2.1.0 type-fest: 0.21.3 typescript: 5.8.3 typescript-memoize: 1.1.1 url-join: 4.0.1 optionalDependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' - encoding - supports-color - '@auto-it/first-time-contributor@11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3)': + '@auto-it/first-time-contributor@11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3)': dependencies: '@auto-it/bot-list': 11.3.0 - '@auto-it/core': 11.3.0(@types/node@22.15.29)(encoding@0.1.13)(typescript@5.8.3) + '@auto-it/core': 11.3.0(@types/node@24.0.0)(encoding@0.1.13)(typescript@5.8.3) array.prototype.flatmap: 1.3.3 endent: 2.1.0 tslib: 2.1.0 @@ -8217,9 +8230,9 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@graphiql/plugin-doc-explorer@0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': + '@graphiql/plugin-doc-explorer@0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: - '@graphiql/react': 0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + '@graphiql/react': 0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) '@headlessui/react': 2.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) graphql: 16.11.0 react: 19.1.0 @@ -8236,10 +8249,10 @@ snapshots: - immer - use-sync-external-store - '@graphiql/plugin-history@0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': + '@graphiql/plugin-history@0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: - '@graphiql/react': 0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) - '@graphiql/toolkit': 0.11.3(@types/node@22.15.29)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0) + '@graphiql/react': 0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + '@graphiql/toolkit': 0.11.3(@types/node@24.0.0)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0) react: 19.1.0 react-compiler-runtime: 19.1.0-rc.1(react@19.1.0) react-dom: 19.1.0(react@19.1.0) @@ -8255,9 +8268,9 @@ snapshots: - immer - use-sync-external-store - '@graphiql/react@0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': + '@graphiql/react@0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: - '@graphiql/toolkit': 0.11.3(@types/node@22.15.29)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0) + '@graphiql/toolkit': 0.11.3(@types/node@24.0.0)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0) '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-dropdown-menu': 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-tooltip': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -8287,11 +8300,11 @@ snapshots: - immer - use-sync-external-store - '@graphiql/toolkit@0.11.3(@types/node@22.15.29)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)': + '@graphiql/toolkit@0.11.3(@types/node@24.0.0)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)': dependencies: '@n1ru4l/push-pull-async-iterable-iterator': 3.2.0 graphql: 16.11.0 - meros: 1.3.0(@types/node@22.15.29) + meros: 1.3.0(@types/node@24.0.0) optionalDependencies: graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)) transitivePeerDependencies: @@ -8303,7 +8316,7 @@ snapshots: graphql: 16.11.0 tslib: 2.6.3 - '@graphql-codegen/cli@5.0.6(@parcel/watcher@2.5.1)(@types/node@22.15.29)(bufferutil@4.0.9)(encoding@0.1.13)(enquirer@2.4.1)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5)': + '@graphql-codegen/cli@5.0.6(@parcel/watcher@2.5.1)(@types/node@24.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(enquirer@2.4.1)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5)': dependencies: '@babel/generator': 7.27.1 '@babel/template': 7.27.2 @@ -8314,12 +8327,12 @@ snapshots: '@graphql-tools/apollo-engine-loader': 8.0.20(graphql@16.11.0) '@graphql-tools/code-file-loader': 8.1.20(graphql@16.11.0) '@graphql-tools/git-loader': 8.0.24(graphql@16.11.0) - '@graphql-tools/github-loader': 8.0.20(@types/node@22.15.29)(graphql@16.11.0) + '@graphql-tools/github-loader': 8.0.20(@types/node@24.0.0)(graphql@16.11.0) '@graphql-tools/graphql-file-loader': 8.0.19(graphql@16.11.0) '@graphql-tools/json-file-loader': 8.0.18(graphql@16.11.0) '@graphql-tools/load': 8.1.0(graphql@16.11.0) - '@graphql-tools/prisma-loader': 8.0.17(@types/node@22.15.29)(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.11.0)(utf-8-validate@6.0.5) - '@graphql-tools/url-loader': 8.0.31(@types/node@22.15.29)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) + '@graphql-tools/prisma-loader': 8.0.17(@types/node@24.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.11.0)(utf-8-validate@6.0.5) + '@graphql-tools/url-loader': 8.0.31(@types/node@24.0.0)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) '@graphql-tools/utils': 10.8.6(graphql@16.11.0) '@whatwg-node/fetch': 0.10.6 chalk: 4.1.2 @@ -8327,7 +8340,7 @@ snapshots: debounce: 1.2.1 detect-indent: 6.1.0 graphql: 16.11.0 - graphql-config: 5.1.5(@types/node@22.15.29)(bufferutil@4.0.9)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5) + graphql-config: 5.1.5(@types/node@24.0.0)(bufferutil@4.0.9)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5) inquirer: 8.2.6 is-glob: 4.0.3 jiti: 1.21.7 @@ -8530,7 +8543,7 @@ snapshots: - uWebSockets.js - utf-8-validate - '@graphql-tools/executor-http@1.3.3(@types/node@22.15.29)(graphql@16.11.0)': + '@graphql-tools/executor-http@1.3.3(@types/node@24.0.0)(graphql@16.11.0)': dependencies: '@graphql-hive/signal': 1.0.0 '@graphql-tools/executor-common': 0.0.4(graphql@16.11.0) @@ -8540,7 +8553,7 @@ snapshots: '@whatwg-node/fetch': 0.10.6 '@whatwg-node/promise-helpers': 1.3.1 graphql: 16.11.0 - meros: 1.3.0(@types/node@22.15.29) + meros: 1.3.0(@types/node@24.0.0) tslib: 2.8.1 transitivePeerDependencies: - '@types/node' @@ -8579,9 +8592,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@graphql-tools/github-loader@8.0.20(@types/node@22.15.29)(graphql@16.11.0)': + '@graphql-tools/github-loader@8.0.20(@types/node@24.0.0)(graphql@16.11.0)': dependencies: - '@graphql-tools/executor-http': 1.3.3(@types/node@22.15.29)(graphql@16.11.0) + '@graphql-tools/executor-http': 1.3.3(@types/node@24.0.0)(graphql@16.11.0) '@graphql-tools/graphql-tag-pluck': 8.3.19(graphql@16.11.0) '@graphql-tools/utils': 10.8.6(graphql@16.11.0) '@whatwg-node/fetch': 0.10.6 @@ -8649,9 +8662,9 @@ snapshots: graphql: 16.11.0 tslib: 2.8.1 - '@graphql-tools/prisma-loader@8.0.17(@types/node@22.15.29)(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.11.0)(utf-8-validate@6.0.5)': + '@graphql-tools/prisma-loader@8.0.17(@types/node@24.0.0)(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.11.0)(utf-8-validate@6.0.5)': dependencies: - '@graphql-tools/url-loader': 8.0.31(@types/node@22.15.29)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) + '@graphql-tools/url-loader': 8.0.31(@types/node@24.0.0)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) '@graphql-tools/utils': 10.8.6(graphql@16.11.0) '@types/js-yaml': 4.0.9 '@whatwg-node/fetch': 0.10.6 @@ -8693,10 +8706,10 @@ snapshots: graphql: 16.11.0 tslib: 2.8.1 - '@graphql-tools/url-loader@8.0.31(@types/node@22.15.29)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5)': + '@graphql-tools/url-loader@8.0.31(@types/node@24.0.0)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5)': dependencies: '@graphql-tools/executor-graphql-ws': 2.0.5(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) - '@graphql-tools/executor-http': 1.3.3(@types/node@22.15.29)(graphql@16.11.0) + '@graphql-tools/executor-http': 1.3.3(@types/node@24.0.0)(graphql@16.11.0) '@graphql-tools/executor-legacy-ws': 1.1.17(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) '@graphql-tools/utils': 10.8.6(graphql@16.11.0) '@graphql-tools/wrap': 10.0.35(graphql@16.11.0) @@ -10533,7 +10546,7 @@ snapshots: optionalDependencies: '@tanstack/react-router': 1.120.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/router-plugin@1.120.15(@rsbuild/core@1.3.22)(@tanstack/react-router@1.120.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))(webpack@5.97.1)': + '@tanstack/router-plugin@1.120.15(@rsbuild/core@1.3.22)(@tanstack/react-router@1.120.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))(webpack@5.97.1)': dependencies: '@babel/core': 7.26.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) @@ -10555,7 +10568,7 @@ snapshots: optionalDependencies: '@rsbuild/core': 1.3.22 '@tanstack/react-router': 1.120.15(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - vite: 5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + vite: 5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) webpack: 5.97.1 transitivePeerDependencies: - supports-color @@ -10638,7 +10651,7 @@ snapshots: '@types/cors@2.8.17': dependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 '@types/d3-array@3.2.1': {} @@ -10699,9 +10712,9 @@ snapshots: dependencies: undici-types: 6.20.0 - '@types/node@22.15.29': + '@types/node@24.0.0': dependencies: - undici-types: 6.21.0 + undici-types: 7.8.0 '@types/parse-json@4.0.2': {} @@ -10727,9 +10740,9 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 - '@vitejs/plugin-react@4.5.1(vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))': + '@vitejs/plugin-react@4.5.1(vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))': dependencies: '@babel/core': 7.27.1 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.27.1) @@ -10737,7 +10750,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.9 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + vite: 5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) transitivePeerDependencies: - supports-color @@ -10748,13 +10761,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))': + '@vitest/mocker@2.1.9(vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0))': dependencies: '@vitest/spy': 2.1.9 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + vite: 5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) '@vitest/pretty-format@2.1.9': dependencies: @@ -11755,7 +11768,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 22.15.29 + '@types/node': 24.0.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -12234,6 +12247,8 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-them-args@1.3.2: {} + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -12290,11 +12305,11 @@ snapshots: graceful-readlink@1.0.1: {} - graphiql@4.1.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): + graphiql@4.1.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): dependencies: - '@graphiql/plugin-doc-explorer': 0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) - '@graphiql/plugin-history': 0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) - '@graphiql/react': 0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@22.15.29)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + '@graphiql/plugin-doc-explorer': 0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + '@graphiql/plugin-history': 0.2.2(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) + '@graphiql/react': 0.34.1(@codemirror/language@6.0.0)(@emotion/is-prop-valid@0.8.8)(@types/node@24.0.0)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(graphql@16.11.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) graphql: 16.11.0 react: 19.1.0 react-compiler-runtime: 19.1.0-rc.1(react@19.1.0) @@ -12309,13 +12324,13 @@ snapshots: - immer - use-sync-external-store - graphql-config@5.1.5(@types/node@22.15.29)(bufferutil@4.0.9)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5): + graphql-config@5.1.5(@types/node@24.0.0)(bufferutil@4.0.9)(graphql@16.11.0)(typescript@5.8.3)(utf-8-validate@6.0.5): dependencies: '@graphql-tools/graphql-file-loader': 8.0.19(graphql@16.11.0) '@graphql-tools/json-file-loader': 8.0.18(graphql@16.11.0) '@graphql-tools/load': 8.1.0(graphql@16.11.0) '@graphql-tools/merge': 9.0.24(graphql@16.11.0) - '@graphql-tools/url-loader': 8.0.31(@types/node@22.15.29)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) + '@graphql-tools/url-loader': 8.0.31(@types/node@24.0.0)(bufferutil@4.0.9)(graphql@16.11.0)(utf-8-validate@6.0.5) '@graphql-tools/utils': 10.8.6(graphql@16.11.0) cosmiconfig: 8.3.6(typescript@5.8.3) graphql: 16.11.0 @@ -12725,7 +12740,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 merge-stream: 2.0.0 supports-color: 8.1.1 optional: true @@ -12811,6 +12826,11 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + kill-port@2.0.1: + dependencies: + get-them-args: 1.3.2 + shell-exec: 1.0.2 + leac@0.6.0: {} lightningcss-darwin-arm64@1.30.1: @@ -12987,9 +13007,9 @@ snapshots: merge2@1.4.1: {} - meros@1.3.0(@types/node@22.15.29): + meros@1.3.0(@types/node@24.0.0): optionalDependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 methods@1.1.2: {} @@ -13956,6 +13976,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-exec@1.0.2: {} + shell-quote@1.8.2: {} side-channel-list@1.0.0: @@ -14336,14 +14358,14 @@ snapshots: ts-log@2.2.7: {} - ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3): + ts-node@10.9.2(@types/node@24.0.0)(typescript@5.8.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.15.29 + '@types/node': 24.0.0 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -14477,7 +14499,7 @@ snapshots: undici-types@6.20.0: {} - undici-types@6.21.0: {} + undici-types@7.8.0: {} universal-user-agent@6.0.1: {} @@ -14596,13 +14618,13 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@2.1.9(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0): + vite-node@2.1.9(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.7.0 pathe: 1.1.2 - vite: 5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + vite: 5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) transitivePeerDependencies: - '@types/node' - less @@ -14614,22 +14636,22 @@ snapshots: - supports-color - terser - vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0): + vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0): dependencies: esbuild: 0.21.5 postcss: 8.5.4 rollup: 4.29.1 optionalDependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 fsevents: 2.3.3 lightningcss: 1.30.1 sass: 1.77.4 terser: 5.41.0 - vitest@2.1.9(@types/node@22.15.29)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@6.0.5))(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0): + vitest@2.1.9(@types/node@24.0.0)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@6.0.5))(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0)) + '@vitest/mocker': 2.1.9(vite@5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -14645,11 +14667,11 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) - vite-node: 2.1.9(@types/node@22.15.29)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + vite: 5.4.11(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) + vite-node: 2.1.9(@types/node@24.0.0)(lightningcss@1.30.1)(sass@1.77.4)(terser@5.41.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.15.29 + '@types/node': 24.0.0 jsdom: 25.0.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - less