refactor: refactor graphql
This commit is contained in:
parent
b09e9e6aaa
commit
258eeddc74
@ -63,7 +63,9 @@ impl Credential3rdCheckAvailableInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_credential3rd_to_schema(mut builder: SeaographyBuilder) -> SeaographyBuilder {
|
||||
pub fn register_credential3rd_to_schema_builder(
|
||||
mut builder: SeaographyBuilder,
|
||||
) -> SeaographyBuilder {
|
||||
builder.schema = builder
|
||||
.schema
|
||||
.register(Credential3rdCheckAvailableInput::generate_input_object());
|
102
apps/recorder/src/graphql/domains/crypto.rs
Normal file
102
apps/recorder/src/graphql/domains/crypto.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_graphql::dynamic::ValueAccessor;
|
||||
use sea_orm::{EntityTrait, Value as SeaValue};
|
||||
use seaography::{BuilderContext, SeaResult};
|
||||
|
||||
use crate::{
|
||||
app::AppContextTrait,
|
||||
graphql::infra::util::{get_column_key, get_entity_key},
|
||||
models::credential_3rd,
|
||||
};
|
||||
|
||||
fn register_crypto_column_input_conversion_to_schema_context<T>(
|
||||
context: &mut BuilderContext,
|
||||
ctx: Arc<dyn AppContextTrait>,
|
||||
column: &T::Column,
|
||||
) where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let column_name = get_column_key::<T>(context, column);
|
||||
let entity_name = context.entity_object.type_name.as_ref()(&entity_key);
|
||||
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &column_name);
|
||||
|
||||
context.types.input_conversions.insert(
|
||||
format!("{entity_name}.{column_name}"),
|
||||
Box::new(move |value: &ValueAccessor| -> SeaResult<sea_orm::Value> {
|
||||
let source = value.string()?;
|
||||
let encrypted = ctx.crypto().encrypt_string(source.into())?;
|
||||
Ok(encrypted.into())
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn register_crypto_column_output_conversion_to_schema_context<T>(
|
||||
context: &mut BuilderContext,
|
||||
ctx: Arc<dyn AppContextTrait>,
|
||||
column: &T::Column,
|
||||
) where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let column_name = get_column_key::<T>(context, column);
|
||||
let entity_name = context.entity_object.type_name.as_ref()(&entity_key);
|
||||
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &column_name);
|
||||
|
||||
context.types.output_conversions.insert(
|
||||
format!("{entity_name}.{column_name}"),
|
||||
Box::new(
|
||||
move |value: &sea_orm::Value| -> SeaResult<async_graphql::Value> {
|
||||
if let SeaValue::String(s) = value {
|
||||
if let Some(s) = s {
|
||||
let decrypted = ctx.crypto().decrypt_string(s)?;
|
||||
Ok(async_graphql::Value::String(decrypted))
|
||||
} else {
|
||||
Ok(async_graphql::Value::Null)
|
||||
}
|
||||
} else {
|
||||
Err(async_graphql::Error::new("crypto column must be string column").into())
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn register_crypto_to_schema_context(
|
||||
context: &mut BuilderContext,
|
||||
ctx: Arc<dyn AppContextTrait>,
|
||||
) {
|
||||
register_crypto_column_input_conversion_to_schema_context::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Cookies,
|
||||
);
|
||||
register_crypto_column_input_conversion_to_schema_context::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Username,
|
||||
);
|
||||
register_crypto_column_input_conversion_to_schema_context::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Password,
|
||||
);
|
||||
register_crypto_column_output_conversion_to_schema_context::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Cookies,
|
||||
);
|
||||
register_crypto_column_output_conversion_to_schema_context::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Username,
|
||||
);
|
||||
register_crypto_column_output_conversion_to_schema_context::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx,
|
||||
&credential_3rd::Column::Password,
|
||||
);
|
||||
}
|
5
apps/recorder/src/graphql/domains/mod.rs
Normal file
5
apps/recorder/src/graphql/domains/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod credential_3rd;
|
||||
pub mod crypto;
|
||||
pub mod subscriber_tasks;
|
||||
pub mod subscribers;
|
||||
pub mod subscriptions;
|
42
apps/recorder/src/graphql/domains/subscriber_tasks.rs
Normal file
42
apps/recorder/src/graphql/domains/subscriber_tasks.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use async_graphql::dynamic::Scalar;
|
||||
use seaography::{Builder as SeaographyBuilder, BuilderContext, ConvertedType};
|
||||
|
||||
use crate::{
|
||||
graphql::infra::{
|
||||
json::restrict_jsonb_filter_input_for_entity,
|
||||
util::{get_column_key, get_entity_key},
|
||||
},
|
||||
models::subscriber_tasks::{self, SubscriberTask},
|
||||
};
|
||||
|
||||
pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext) {
|
||||
let entity_key = get_entity_key::<subscriber_tasks::Entity>(context);
|
||||
let column_name =
|
||||
get_column_key::<subscriber_tasks::Entity>(context, &subscriber_tasks::Column::Job);
|
||||
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &column_name);
|
||||
context.types.overwrites.insert(
|
||||
column_name,
|
||||
ConvertedType::Custom(String::from("SubscriberTask")),
|
||||
);
|
||||
restrict_jsonb_filter_input_for_entity::<subscriber_tasks::Entity>(
|
||||
context,
|
||||
&subscriber_tasks::Column::Job,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn register_subscriber_tasks_to_schema_builder(
|
||||
mut builder: SeaographyBuilder,
|
||||
) -> SeaographyBuilder {
|
||||
let subscriber_tasks_scalar = Scalar::new("SubscriberTasks")
|
||||
.description("The subscriber tasks")
|
||||
.validator(|value| -> bool {
|
||||
if let Ok(json) = value.clone().into_json() {
|
||||
serde_json::from_value::<SubscriberTask>(json).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
builder.schema = builder.schema.register(subscriber_tasks_scalar);
|
||||
builder
|
||||
}
|
@ -1,16 +1,20 @@
|
||||
use async_graphql::dynamic::ObjectAccessor;
|
||||
use once_cell::sync::OnceCell;
|
||||
use sea_orm::{ColumnTrait, Condition, EntityTrait};
|
||||
use seaography::{
|
||||
BuilderContext, FilterInfo, FilterOperation as SeaographqlFilterOperation, SeaResult,
|
||||
};
|
||||
use async_graphql::dynamic::TypeRef;
|
||||
use lazy_static::lazy_static;
|
||||
use maplit::btreeset;
|
||||
use sea_orm::{ColumnTrait, EntityTrait};
|
||||
use seaography::{BuilderContext, FilterInfo, FilterOperation as SeaographqlFilterOperation};
|
||||
|
||||
pub static SUBSCRIBER_ID_FILTER_INFO: OnceCell<FilterInfo> = OnceCell::new();
|
||||
use crate::graphql::infra::filter::FnFilterCondition;
|
||||
|
||||
pub type FnFilterCondition =
|
||||
Box<dyn Fn(Condition, &ObjectAccessor) -> SeaResult<Condition> + Send + Sync>;
|
||||
lazy_static! {
|
||||
pub static ref SUBSCRIBER_ID_FILTER_INFO: FilterInfo = FilterInfo {
|
||||
type_name: String::from("SubscriberIdFilterInput"),
|
||||
base_type: TypeRef::INT.into(),
|
||||
supported_operations: btreeset! { SeaographqlFilterOperation::Equals },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn subscriber_id_condition_function<T>(
|
||||
pub fn generate_subscriber_id_condition_function<T>(
|
||||
_context: &BuilderContext,
|
||||
column: &T::Column,
|
||||
) -> FnFilterCondition
|
||||
@ -20,9 +24,7 @@ where
|
||||
{
|
||||
let column = *column;
|
||||
Box::new(move |mut condition, filter| {
|
||||
let subscriber_id_filter_info = SUBSCRIBER_ID_FILTER_INFO.get().unwrap();
|
||||
let operations = &subscriber_id_filter_info.supported_operations;
|
||||
for operation in operations {
|
||||
for operation in &SUBSCRIBER_ID_FILTER_INFO.supported_operations {
|
||||
match operation {
|
||||
SeaographqlFilterOperation::Equals => {
|
||||
if let Some(value) = filter.get("eq") {
|
94
apps/recorder/src/graphql/domains/subscribers/mod.rs
Normal file
94
apps/recorder/src/graphql/domains/subscribers/mod.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use sea_orm::{EntityTrait, Iterable};
|
||||
use seaography::{Builder as SeaographyBuilder, BuilderContext, FilterType, FilterTypesMapHelper};
|
||||
|
||||
mod filter;
|
||||
mod guard;
|
||||
mod transformer;
|
||||
|
||||
use filter::{SUBSCRIBER_ID_FILTER_INFO, generate_subscriber_id_condition_function};
|
||||
use guard::{guard_entity_with_subscriber_id, guard_field_with_subscriber_id};
|
||||
use transformer::{
|
||||
generate_subscriber_id_filter_condition_transformer,
|
||||
generate_subscriber_id_mutation_input_object_transformer,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
graphql::infra::util::{get_entity_column_key, get_entity_key},
|
||||
models::subscribers,
|
||||
};
|
||||
|
||||
pub fn restrict_subscriber_for_entity<T>(context: &mut BuilderContext, column: &T::Column)
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let entity_column_key = get_entity_column_key::<T>(context, column);
|
||||
context.guards.entity_guards.insert(
|
||||
entity_key.clone(),
|
||||
guard_entity_with_subscriber_id::<T>(context, column),
|
||||
);
|
||||
context.guards.field_guards.insert(
|
||||
entity_column_key.clone(),
|
||||
guard_field_with_subscriber_id::<T>(context, column),
|
||||
);
|
||||
context.filter_types.overwrites.insert(
|
||||
entity_column_key.clone(),
|
||||
Some(FilterType::Custom(
|
||||
SUBSCRIBER_ID_FILTER_INFO.type_name.clone(),
|
||||
)),
|
||||
);
|
||||
context.filter_types.condition_functions.insert(
|
||||
entity_column_key.clone(),
|
||||
generate_subscriber_id_condition_function::<T>(context, column),
|
||||
);
|
||||
context.transformers.filter_conditions_transformers.insert(
|
||||
entity_key.clone(),
|
||||
generate_subscriber_id_filter_condition_transformer::<T>(context, column),
|
||||
);
|
||||
context
|
||||
.transformers
|
||||
.mutation_input_object_transformers
|
||||
.insert(
|
||||
entity_key,
|
||||
generate_subscriber_id_mutation_input_object_transformer::<T>(context, column),
|
||||
);
|
||||
context
|
||||
.entity_input
|
||||
.insert_skips
|
||||
.push(entity_column_key.clone());
|
||||
context.entity_input.update_skips.push(entity_column_key);
|
||||
}
|
||||
|
||||
pub fn register_subscribers_to_schema_context(context: &mut BuilderContext) {
|
||||
for column in subscribers::Column::iter() {
|
||||
if !matches!(column, subscribers::Column::Id) {
|
||||
let key = get_entity_column_key::<subscribers::Entity>(context, &column);
|
||||
context.filter_types.overwrites.insert(key, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_subscribers_to_schema_builder(mut builder: SeaographyBuilder) -> SeaographyBuilder {
|
||||
{
|
||||
let filter_types_map_helper = FilterTypesMapHelper {
|
||||
context: builder.context,
|
||||
};
|
||||
|
||||
builder.schema = builder
|
||||
.schema
|
||||
.register(filter_types_map_helper.generate_filter_input(&SUBSCRIBER_ID_FILTER_INFO));
|
||||
}
|
||||
|
||||
{
|
||||
builder.register_entity::<subscribers::Entity>(
|
||||
<subscribers::RelatedEntity as sea_orm::Iterable>::iter()
|
||||
.map(|rel| seaography::RelationBuilder::get_relation(&rel, builder.context))
|
||||
.collect(),
|
||||
);
|
||||
builder = builder.register_entity_dataloader_one_to_one(subscribers::Entity, tokio::spawn);
|
||||
builder = builder.register_entity_dataloader_one_to_many(subscribers::Entity, tokio::spawn);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
85
apps/recorder/src/graphql/domains/subscribers/transformer.rs
Normal file
85
apps/recorder/src/graphql/domains/subscribers/transformer.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use async_graphql::dynamic::ResolverContext;
|
||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, Value as SeaValue};
|
||||
use seaography::{BuilderContext, FnFilterConditionsTransformer, FnMutationInputObjectTransformer};
|
||||
|
||||
use crate::{
|
||||
auth::AuthUserInfo,
|
||||
graphql::infra::util::{get_column_key, get_entity_key},
|
||||
};
|
||||
|
||||
pub fn generate_subscriber_id_filter_condition_transformer<T>(
|
||||
_context: &BuilderContext,
|
||||
column: &T::Column,
|
||||
) -> FnFilterConditionsTransformer
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let column = *column;
|
||||
Box::new(
|
||||
move |context: &ResolverContext, condition: Condition| -> Condition {
|
||||
match context.ctx.data::<AuthUserInfo>() {
|
||||
Ok(user_info) => {
|
||||
let subscriber_id = user_info.subscriber_auth.subscriber_id;
|
||||
condition.add(column.eq(subscriber_id))
|
||||
}
|
||||
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_subscriber_id_mutation_input_object_transformer<T>(
|
||||
context: &BuilderContext,
|
||||
column: &T::Column,
|
||||
) -> FnMutationInputObjectTransformer
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let entity_name = context.entity_query_field.type_name.as_ref()(&entity_key);
|
||||
let column_key = get_column_key::<T>(context, column);
|
||||
let column_name = Arc::new(context.entity_object.column_name.as_ref()(
|
||||
&entity_key,
|
||||
&column_key,
|
||||
));
|
||||
let entity_create_one_mutation_field_name = Arc::new(format!(
|
||||
"{}{}",
|
||||
entity_name, context.entity_create_one_mutation.mutation_suffix
|
||||
));
|
||||
let entity_create_batch_mutation_field_name = Arc::new(format!(
|
||||
"{}{}",
|
||||
entity_name,
|
||||
context.entity_create_batch_mutation.mutation_suffix.clone()
|
||||
));
|
||||
Box::new(
|
||||
move |context: &ResolverContext,
|
||||
mut input: BTreeMap<String, SeaValue>|
|
||||
-> BTreeMap<String, SeaValue> {
|
||||
let field_name = context.field().name();
|
||||
if field_name == entity_create_one_mutation_field_name.as_str()
|
||||
|| field_name == entity_create_batch_mutation_field_name.as_str()
|
||||
{
|
||||
match context.ctx.data::<AuthUserInfo>() {
|
||||
Ok(user_info) => {
|
||||
let subscriber_id = user_info.subscriber_auth.subscriber_id;
|
||||
let value = input.get_mut(column_name.as_str());
|
||||
if value.is_none() {
|
||||
input.insert(
|
||||
column_name.as_str().to_string(),
|
||||
SeaValue::Int(Some(subscriber_id)),
|
||||
);
|
||||
}
|
||||
input
|
||||
}
|
||||
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
|
||||
}
|
||||
} else {
|
||||
input
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
@ -66,7 +66,9 @@ impl SyncOneSubscriptionInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_subscriptions_to_schema(mut builder: SeaographyBuilder) -> SeaographyBuilder {
|
||||
pub fn register_subscriptions_to_schema_builder(
|
||||
mut builder: SeaographyBuilder,
|
||||
) -> SeaographyBuilder {
|
||||
builder.schema = builder
|
||||
.schema
|
||||
.register(SyncOneSubscriptionFilterInput::generate_input_object());
|
6
apps/recorder/src/graphql/infra/filter.rs
Normal file
6
apps/recorder/src/graphql/infra/filter.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use async_graphql::dynamic::ObjectAccessor;
|
||||
use sea_orm::Condition;
|
||||
use seaography::SeaResult;
|
||||
|
||||
pub type FnFilterCondition =
|
||||
Box<dyn Fn(Condition, &ObjectAccessor) -> SeaResult<Condition> + Send + Sync>;
|
@ -1,19 +0,0 @@
|
||||
mod json;
|
||||
mod subscriber;
|
||||
|
||||
use async_graphql::dynamic::TypeRef;
|
||||
pub use json::{
|
||||
JSONB_FILTER_NAME, jsonb_filter_condition_function,
|
||||
register_jsonb_input_filter_to_dynamic_schema,
|
||||
};
|
||||
use maplit::btreeset;
|
||||
use seaography::{FilterInfo, FilterOperation as SeaographqlFilterOperation};
|
||||
pub use subscriber::{SUBSCRIBER_ID_FILTER_INFO, subscriber_id_condition_function};
|
||||
|
||||
pub fn init_custom_filter_info() {
|
||||
SUBSCRIBER_ID_FILTER_INFO.get_or_init(|| FilterInfo {
|
||||
type_name: String::from("SubscriberIdFilterInput"),
|
||||
base_type: TypeRef::INT.into(),
|
||||
supported_operations: btreeset! { SeaographqlFilterOperation::Equals },
|
||||
});
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
use async_graphql::{
|
||||
Error as GraphqlError,
|
||||
dynamic::{Scalar, SchemaBuilder, SchemaError},
|
||||
dynamic::{Scalar, SchemaError},
|
||||
to_value,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@ -9,10 +9,13 @@ use sea_orm::{
|
||||
Condition, EntityTrait,
|
||||
sea_query::{ArrayType, Expr, ExprTrait, IntoLikeExpr, SimpleExpr, Value as DbValue},
|
||||
};
|
||||
use seaography::{BuilderContext, SeaographyError};
|
||||
use seaography::{Builder as SeaographyBuilder, BuilderContext, FilterType, SeaographyError};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::{errors::RecorderResult, graphql::infra::filter::subscriber::FnFilterCondition};
|
||||
use crate::{
|
||||
errors::RecorderResult,
|
||||
graphql::infra::{filter::FnFilterCondition, util::get_entity_column_key},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||
pub enum JsonbFilterOperation {
|
||||
@ -892,7 +895,7 @@ where
|
||||
|
||||
pub const JSONB_FILTER_NAME: &str = "JsonbFilterInput";
|
||||
|
||||
pub fn jsonb_filter_condition_function<T>(
|
||||
pub fn generate_jsonb_filter_condition_function<T>(
|
||||
_context: &BuilderContext,
|
||||
column: &T::Column,
|
||||
) -> FnFilterCondition
|
||||
@ -917,11 +920,24 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_jsonb_input_filter_to_dynamic_schema(
|
||||
schema_builder: SchemaBuilder,
|
||||
) -> SchemaBuilder {
|
||||
pub fn register_jsonb_input_filter_to_schema_builder(
|
||||
mut builder: SeaographyBuilder,
|
||||
) -> SeaographyBuilder {
|
||||
let json_filter_input_type = Scalar::new(JSONB_FILTER_NAME);
|
||||
schema_builder.register(json_filter_input_type)
|
||||
builder.schema = builder.schema.register(json_filter_input_type);
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn restrict_jsonb_filter_input_for_entity<T>(context: &mut BuilderContext, column: &T::Column)
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_column_key = get_entity_column_key::<T>(context, column);
|
||||
context.filter_types.overwrites.insert(
|
||||
entity_column_key.clone(),
|
||||
Some(FilterType::Custom(JSONB_FILTER_NAME.to_string())),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
@ -1,6 +1,3 @@
|
||||
pub mod filter;
|
||||
pub mod guard;
|
||||
pub mod order;
|
||||
pub mod pagination;
|
||||
pub mod transformer;
|
||||
pub mod json;
|
||||
pub mod util;
|
||||
pub mod filter;
|
||||
|
@ -1,36 +0,0 @@
|
||||
use async_graphql::{InputObject, SimpleObject};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, InputObject)]
|
||||
pub struct CursorInput {
|
||||
pub cursor: Option<String>,
|
||||
pub limit: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, InputObject)]
|
||||
pub struct PageInput {
|
||||
pub page: u64,
|
||||
pub limit: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, InputObject)]
|
||||
pub struct OffsetInput {
|
||||
pub offset: u64,
|
||||
pub limit: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, InputObject)]
|
||||
pub struct PaginationInput {
|
||||
pub cursor: Option<CursorInput>,
|
||||
pub page: Option<PageInput>,
|
||||
pub offset: Option<OffsetInput>,
|
||||
}
|
||||
|
||||
pub type PageInfo = async_graphql::connection::PageInfo;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, SimpleObject)]
|
||||
pub struct PaginationInfo {
|
||||
pub pages: u64,
|
||||
pub current: u64,
|
||||
pub offset: u64,
|
||||
pub total: u64,
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use async_graphql::dynamic::{ResolverContext, ValueAccessor};
|
||||
use sea_orm::{ColumnTrait, Condition, EntityTrait, Value as SeaValue};
|
||||
use seaography::{
|
||||
BuilderContext, FnFilterConditionsTransformer, FnMutationInputObjectTransformer, SeaResult,
|
||||
};
|
||||
|
||||
use super::util::{get_column_key, get_entity_key};
|
||||
use crate::{app::AppContextTrait, auth::AuthUserInfo, models::credential_3rd};
|
||||
|
||||
pub fn build_filter_condition_transformer<T>(
|
||||
_context: &BuilderContext,
|
||||
column: &T::Column,
|
||||
) -> FnFilterConditionsTransformer
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let column = *column;
|
||||
Box::new(
|
||||
move |context: &ResolverContext, condition: Condition| -> Condition {
|
||||
match context.ctx.data::<AuthUserInfo>() {
|
||||
Ok(user_info) => {
|
||||
let subscriber_id = user_info.subscriber_auth.subscriber_id;
|
||||
condition.add(column.eq(subscriber_id))
|
||||
}
|
||||
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_mutation_input_object_transformer<T>(
|
||||
context: &BuilderContext,
|
||||
column: &T::Column,
|
||||
) -> FnMutationInputObjectTransformer
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let entity_name = context.entity_query_field.type_name.as_ref()(&entity_key);
|
||||
let column_key = get_column_key::<T>(context, column);
|
||||
let column_name = Arc::new(context.entity_object.column_name.as_ref()(
|
||||
&entity_key,
|
||||
&column_key,
|
||||
));
|
||||
let entity_create_one_mutation_field_name = Arc::new(format!(
|
||||
"{}{}",
|
||||
entity_name, context.entity_create_one_mutation.mutation_suffix
|
||||
));
|
||||
let entity_create_batch_mutation_field_name = Arc::new(format!(
|
||||
"{}{}",
|
||||
entity_name,
|
||||
context.entity_create_batch_mutation.mutation_suffix.clone()
|
||||
));
|
||||
Box::new(
|
||||
move |context: &ResolverContext,
|
||||
mut input: BTreeMap<String, SeaValue>|
|
||||
-> BTreeMap<String, SeaValue> {
|
||||
let field_name = context.field().name();
|
||||
if field_name == entity_create_one_mutation_field_name.as_str()
|
||||
|| field_name == entity_create_batch_mutation_field_name.as_str()
|
||||
{
|
||||
match context.ctx.data::<AuthUserInfo>() {
|
||||
Ok(user_info) => {
|
||||
let subscriber_id = user_info.subscriber_auth.subscriber_id;
|
||||
let value = input.get_mut(column_name.as_str());
|
||||
if value.is_none() {
|
||||
input.insert(
|
||||
column_name.as_str().to_string(),
|
||||
SeaValue::Int(Some(subscriber_id)),
|
||||
);
|
||||
}
|
||||
input
|
||||
}
|
||||
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
|
||||
}
|
||||
} else {
|
||||
input
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn add_crypto_column_input_conversion<T>(
|
||||
context: &mut BuilderContext,
|
||||
ctx: Arc<dyn AppContextTrait>,
|
||||
column: &T::Column,
|
||||
) where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let column_name = get_column_key::<T>(context, column);
|
||||
let entity_name = context.entity_object.type_name.as_ref()(&entity_key);
|
||||
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &column_name);
|
||||
|
||||
context.types.input_conversions.insert(
|
||||
format!("{entity_name}.{column_name}"),
|
||||
Box::new(move |value: &ValueAccessor| -> SeaResult<sea_orm::Value> {
|
||||
let source = value.string()?;
|
||||
let encrypted = ctx.crypto().encrypt_string(source.into())?;
|
||||
Ok(encrypted.into())
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn add_crypto_column_output_conversion<T>(
|
||||
context: &mut BuilderContext,
|
||||
ctx: Arc<dyn AppContextTrait>,
|
||||
column: &T::Column,
|
||||
) where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let column_name = get_column_key::<T>(context, column);
|
||||
let entity_name = context.entity_object.type_name.as_ref()(&entity_key);
|
||||
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &column_name);
|
||||
|
||||
context.types.output_conversions.insert(
|
||||
format!("{entity_name}.{column_name}"),
|
||||
Box::new(
|
||||
move |value: &sea_orm::Value| -> SeaResult<async_graphql::Value> {
|
||||
if let SeaValue::String(s) = value {
|
||||
if let Some(s) = s {
|
||||
let decrypted = ctx.crypto().decrypt_string(s)?;
|
||||
Ok(async_graphql::Value::String(decrypted))
|
||||
} else {
|
||||
Ok(async_graphql::Value::Null)
|
||||
}
|
||||
} else {
|
||||
Err(async_graphql::Error::new("crypto column must be string column").into())
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_crypto_transformers(context: &mut BuilderContext, ctx: Arc<dyn AppContextTrait>) {
|
||||
add_crypto_column_input_conversion::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Cookies,
|
||||
);
|
||||
add_crypto_column_input_conversion::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Username,
|
||||
);
|
||||
add_crypto_column_input_conversion::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Password,
|
||||
);
|
||||
add_crypto_column_output_conversion::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Cookies,
|
||||
);
|
||||
add_crypto_column_output_conversion::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx.clone(),
|
||||
&credential_3rd::Column::Username,
|
||||
);
|
||||
add_crypto_column_output_conversion::<credential_3rd::Entity>(
|
||||
context,
|
||||
ctx,
|
||||
&credential_3rd::Column::Password,
|
||||
);
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
pub mod config;
|
||||
pub mod domains;
|
||||
pub mod infra;
|
||||
mod schema;
|
||||
pub mod service;
|
||||
pub mod views;
|
||||
|
||||
pub use config::GraphQLConfig;
|
||||
pub use schema::build_schema;
|
||||
|
@ -2,97 +2,30 @@ use std::sync::Arc;
|
||||
|
||||
use async_graphql::dynamic::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use sea_orm::{EntityTrait, Iterable};
|
||||
use seaography::{Builder, BuilderContext, FilterType, FilterTypesMapHelper};
|
||||
use seaography::{Builder, BuilderContext};
|
||||
|
||||
use crate::{
|
||||
app::AppContextTrait,
|
||||
graphql::{
|
||||
infra::{
|
||||
filter::{
|
||||
JSONB_FILTER_NAME, SUBSCRIBER_ID_FILTER_INFO, init_custom_filter_info,
|
||||
register_jsonb_input_filter_to_dynamic_schema, subscriber_id_condition_function,
|
||||
domains::{
|
||||
credential_3rd::register_credential3rd_to_schema_builder,
|
||||
crypto::register_crypto_to_schema_context,
|
||||
subscriber_tasks::{
|
||||
register_subscriber_tasks_to_schema_builder,
|
||||
register_subscriber_tasks_to_schema_context,
|
||||
},
|
||||
guard::{guard_entity_with_subscriber_id, guard_field_with_subscriber_id},
|
||||
transformer::{
|
||||
add_crypto_transformers, build_filter_condition_transformer,
|
||||
build_mutation_input_object_transformer,
|
||||
subscribers::{
|
||||
register_subscribers_to_schema_builder, register_subscribers_to_schema_context,
|
||||
restrict_subscriber_for_entity,
|
||||
},
|
||||
util::{get_entity_column_key, get_entity_key},
|
||||
subscriptions::register_subscriptions_to_schema_builder,
|
||||
},
|
||||
views::{register_credential3rd_to_schema, register_subscriptions_to_schema},
|
||||
infra::json::register_jsonb_input_filter_to_schema_builder,
|
||||
},
|
||||
};
|
||||
|
||||
pub static CONTEXT: OnceCell<BuilderContext> = OnceCell::new();
|
||||
|
||||
fn restrict_filter_input_for_entity<T>(
|
||||
context: &mut BuilderContext,
|
||||
column: &T::Column,
|
||||
filter_type: Option<FilterType>,
|
||||
) where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let key = get_entity_column_key::<T>(context, column);
|
||||
context.filter_types.overwrites.insert(key, filter_type);
|
||||
}
|
||||
|
||||
fn restrict_jsonb_filter_input_for_entity<T>(context: &mut BuilderContext, column: &T::Column)
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_column_key = get_entity_column_key::<T>(context, column);
|
||||
context.filter_types.overwrites.insert(
|
||||
entity_column_key.clone(),
|
||||
Some(FilterType::Custom(JSONB_FILTER_NAME.to_string())),
|
||||
);
|
||||
}
|
||||
|
||||
fn restrict_subscriber_for_entity<T>(context: &mut BuilderContext, column: &T::Column)
|
||||
where
|
||||
T: EntityTrait,
|
||||
<T as EntityTrait>::Model: Sync,
|
||||
{
|
||||
let entity_key = get_entity_key::<T>(context);
|
||||
let entity_column_key = get_entity_column_key::<T>(context, column);
|
||||
context.guards.entity_guards.insert(
|
||||
entity_key.clone(),
|
||||
guard_entity_with_subscriber_id::<T>(context, column),
|
||||
);
|
||||
context.guards.field_guards.insert(
|
||||
entity_column_key.clone(),
|
||||
guard_field_with_subscriber_id::<T>(context, column),
|
||||
);
|
||||
context.filter_types.overwrites.insert(
|
||||
entity_column_key.clone(),
|
||||
Some(FilterType::Custom(
|
||||
SUBSCRIBER_ID_FILTER_INFO.get().unwrap().type_name.clone(),
|
||||
)),
|
||||
);
|
||||
context.filter_types.condition_functions.insert(
|
||||
entity_column_key.clone(),
|
||||
subscriber_id_condition_function::<T>(context, column),
|
||||
);
|
||||
context.transformers.filter_conditions_transformers.insert(
|
||||
entity_key.clone(),
|
||||
build_filter_condition_transformer::<T>(context, column),
|
||||
);
|
||||
context
|
||||
.transformers
|
||||
.mutation_input_object_transformers
|
||||
.insert(
|
||||
entity_key,
|
||||
build_mutation_input_object_transformer::<T>(context, column),
|
||||
);
|
||||
context
|
||||
.entity_input
|
||||
.insert_skips
|
||||
.push(entity_column_key.clone());
|
||||
context.entity_input.update_skips.push(entity_column_key);
|
||||
}
|
||||
|
||||
pub fn build_schema(
|
||||
app_ctx: Arc<dyn AppContextTrait>,
|
||||
depth: Option<usize>,
|
||||
@ -101,70 +34,58 @@ pub fn build_schema(
|
||||
use crate::models::*;
|
||||
let database = app_ctx.db().as_ref().clone();
|
||||
|
||||
init_custom_filter_info();
|
||||
let context = CONTEXT.get_or_init(|| {
|
||||
let mut context = BuilderContext::default();
|
||||
|
||||
context.pagination_input.type_name = "PaginationInput".to_string();
|
||||
context.pagination_info_object.type_name = "PaginationInfo".to_string();
|
||||
context.cursor_input.type_name = "CursorInput".to_string();
|
||||
context.offset_input.type_name = "OffsetInput".to_string();
|
||||
context.page_input.type_name = "PageInput".to_string();
|
||||
context.page_info_object.type_name = "PageInfo".to_string();
|
||||
{
|
||||
// domains
|
||||
register_subscribers_to_schema_context(&mut context);
|
||||
|
||||
restrict_subscriber_for_entity::<bangumi::Entity>(
|
||||
&mut context,
|
||||
&bangumi::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<downloaders::Entity>(
|
||||
&mut context,
|
||||
&downloaders::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<downloads::Entity>(
|
||||
&mut context,
|
||||
&downloads::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<episodes::Entity>(
|
||||
&mut context,
|
||||
&episodes::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscriptions::Entity>(
|
||||
&mut context,
|
||||
&subscriptions::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscribers::Entity>(
|
||||
&mut context,
|
||||
&subscribers::Column::Id,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscription_bangumi::Entity>(
|
||||
&mut context,
|
||||
&subscription_bangumi::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscription_episode::Entity>(
|
||||
&mut context,
|
||||
&subscription_episode::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscriber_tasks::Entity>(
|
||||
&mut context,
|
||||
&subscriber_tasks::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<credential_3rd::Entity>(
|
||||
&mut context,
|
||||
&credential_3rd::Column::SubscriberId,
|
||||
);
|
||||
restrict_jsonb_filter_input_for_entity::<subscriber_tasks::Entity>(
|
||||
&mut context,
|
||||
&subscriber_tasks::Column::Job,
|
||||
);
|
||||
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::<subscribers::Entity>(
|
||||
{
|
||||
restrict_subscriber_for_entity::<bangumi::Entity>(
|
||||
&mut context,
|
||||
&column,
|
||||
None,
|
||||
&bangumi::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<downloaders::Entity>(
|
||||
&mut context,
|
||||
&downloaders::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<downloads::Entity>(
|
||||
&mut context,
|
||||
&downloads::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<episodes::Entity>(
|
||||
&mut context,
|
||||
&episodes::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscriptions::Entity>(
|
||||
&mut context,
|
||||
&subscriptions::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscribers::Entity>(
|
||||
&mut context,
|
||||
&subscribers::Column::Id,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscription_bangumi::Entity>(
|
||||
&mut context,
|
||||
&subscription_bangumi::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscription_episode::Entity>(
|
||||
&mut context,
|
||||
&subscription_episode::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<subscriber_tasks::Entity>(
|
||||
&mut context,
|
||||
&subscriber_tasks::Column::SubscriberId,
|
||||
);
|
||||
restrict_subscriber_for_entity::<credential_3rd::Entity>(
|
||||
&mut context,
|
||||
&credential_3rd::Column::SubscriberId,
|
||||
);
|
||||
}
|
||||
|
||||
register_crypto_to_schema_context(&mut context, app_ctx.clone());
|
||||
register_subscriber_tasks_to_schema_context(&mut context);
|
||||
}
|
||||
context
|
||||
});
|
||||
@ -172,50 +93,39 @@ pub fn build_schema(
|
||||
let mut builder = Builder::new(context, database.clone());
|
||||
|
||||
{
|
||||
let filter_types_map_helper = FilterTypesMapHelper { context };
|
||||
// infra
|
||||
builder = register_jsonb_input_filter_to_schema_builder(builder);
|
||||
}
|
||||
{
|
||||
// domains
|
||||
builder = register_subscribers_to_schema_builder(builder);
|
||||
|
||||
builder.schema = builder.schema.register(
|
||||
filter_types_map_helper.generate_filter_input(SUBSCRIBER_ID_FILTER_INFO.get().unwrap()),
|
||||
seaography::register_entities!(
|
||||
builder,
|
||||
[
|
||||
bangumi,
|
||||
downloaders,
|
||||
downloads,
|
||||
episodes,
|
||||
subscription_bangumi,
|
||||
subscription_episode,
|
||||
subscriptions,
|
||||
subscriber_tasks,
|
||||
credential_3rd
|
||||
]
|
||||
);
|
||||
builder.schema = register_jsonb_input_filter_to_dynamic_schema(builder.schema);
|
||||
}
|
||||
|
||||
{
|
||||
builder.register_entity::<subscribers::Entity>(
|
||||
<subscribers::RelatedEntity as sea_orm::Iterable>::iter()
|
||||
.map(|rel| seaography::RelationBuilder::get_relation(&rel, builder.context))
|
||||
.collect(),
|
||||
);
|
||||
builder = builder.register_entity_dataloader_one_to_one(subscribers::Entity, tokio::spawn);
|
||||
builder = builder.register_entity_dataloader_one_to_many(subscribers::Entity, tokio::spawn);
|
||||
}
|
||||
{
|
||||
builder.register_enumeration::<downloads::DownloadStatus>();
|
||||
builder.register_enumeration::<subscriptions::SubscriptionCategory>();
|
||||
builder.register_enumeration::<downloaders::DownloaderCategory>();
|
||||
builder.register_enumeration::<downloads::DownloadMime>();
|
||||
builder.register_enumeration::<credential_3rd::Credential3rdType>();
|
||||
}
|
||||
|
||||
seaography::register_entities!(
|
||||
builder,
|
||||
[
|
||||
bangumi,
|
||||
downloaders,
|
||||
downloads,
|
||||
episodes,
|
||||
subscription_bangumi,
|
||||
subscription_episode,
|
||||
subscriptions,
|
||||
subscriber_tasks,
|
||||
credential_3rd
|
||||
]
|
||||
);
|
||||
|
||||
{
|
||||
builder.register_enumeration::<downloads::DownloadStatus>();
|
||||
builder.register_enumeration::<subscriptions::SubscriptionCategory>();
|
||||
builder.register_enumeration::<downloaders::DownloaderCategory>();
|
||||
builder.register_enumeration::<downloads::DownloadMime>();
|
||||
builder.register_enumeration::<credential_3rd::Credential3rdType>();
|
||||
}
|
||||
|
||||
{
|
||||
builder = register_subscriptions_to_schema(builder);
|
||||
builder = register_credential3rd_to_schema(builder);
|
||||
builder = register_subscriptions_to_schema_builder(builder);
|
||||
builder = register_credential3rd_to_schema_builder(builder);
|
||||
builder = register_subscriber_tasks_to_schema_builder(builder);
|
||||
}
|
||||
|
||||
let schema = builder.schema_builder();
|
||||
|
@ -1,5 +0,0 @@
|
||||
mod credential_3rd;
|
||||
mod subscription;
|
||||
|
||||
pub use credential_3rd::register_credential3rd_to_schema;
|
||||
pub use subscription::register_subscriptions_to_schema;
|
@ -1,6 +1,6 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
use crate::task::SubscriberTask;
|
||||
pub use crate::task::SubscriberTask;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "subscriber_tasks")]
|
||||
|
@ -137,6 +137,7 @@
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
|
136
apps/webui/src/app/config/nav.ts
Normal file
136
apps/webui/src/app/config/nav.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import type { NavMainGroup } from '@/infra/routes/nav';
|
||||
import {
|
||||
BookOpen,
|
||||
Folders,
|
||||
KeyRound,
|
||||
ListTodo,
|
||||
Settings2,
|
||||
SquareTerminal,
|
||||
Telescope,
|
||||
Tv,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const AppNavMainData: NavMainGroup[] = [
|
||||
{
|
||||
group: 'Dashboard',
|
||||
items: [
|
||||
{
|
||||
title: 'Explore',
|
||||
icon: Telescope,
|
||||
link: {
|
||||
to: '/explore',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Subscriptions',
|
||||
link: {
|
||||
to: '/subscriptions/manage',
|
||||
},
|
||||
icon: Folders,
|
||||
children: [
|
||||
{
|
||||
title: 'Manage',
|
||||
link: {
|
||||
to: '/subscriptions/manage',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Create',
|
||||
link: {
|
||||
to: '/subscriptions/create',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Bangumi',
|
||||
icon: Tv,
|
||||
children: [
|
||||
{
|
||||
title: 'Manage',
|
||||
link: {
|
||||
to: '/bangumi/recorder',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Feed',
|
||||
link: {
|
||||
to: '/bangumi/feed',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Tasks',
|
||||
icon: ListTodo,
|
||||
children: [
|
||||
{
|
||||
title: 'Manage',
|
||||
link: {
|
||||
to: '/task/manage',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Credential 3rd',
|
||||
link: {
|
||||
to: '/credential3rd/manage',
|
||||
},
|
||||
icon: KeyRound,
|
||||
children: [
|
||||
{
|
||||
title: 'Manage',
|
||||
link: {
|
||||
to: '/credential3rd/manage',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Create',
|
||||
link: {
|
||||
to: '/credential3rd/create',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Playground',
|
||||
icon: SquareTerminal,
|
||||
link: {
|
||||
to: '/playground',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: 'GraphQL Api',
|
||||
link: {
|
||||
to: '/playground/graphql-api',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
link: {
|
||||
href: 'https://github.com/dumtruck/konobangu/wiki',
|
||||
target: '_blank',
|
||||
},
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
link: {
|
||||
to: '/settings',
|
||||
},
|
||||
icon: Settings2,
|
||||
children: [
|
||||
{
|
||||
title: 'Downloader',
|
||||
link: {
|
||||
to: '/settings/downloader',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
@ -1,3 +1,4 @@
|
||||
import { AppNavMainData } from '@/app/config/nav';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@ -5,7 +6,6 @@ import {
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { AppNavMainData } from '@/infra/routes/nav';
|
||||
import type { ComponentPropsWithoutRef } from 'react';
|
||||
import { AppIcon } from './app-icon';
|
||||
import { NavMain } from './nav-main';
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { ChevronRight, type LucideIcon } from 'lucide-react';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
@ -26,20 +26,9 @@ import {
|
||||
SidebarMenuSubItem,
|
||||
useSidebar,
|
||||
} from '@/components/ui/sidebar';
|
||||
import type { NavMainGroup, NavMainItem } from '@/infra/routes/nav';
|
||||
import { useMatches } from '@tanstack/react-router';
|
||||
|
||||
export interface NavMainItem {
|
||||
link?: ProLinkProps;
|
||||
title: string;
|
||||
icon?: LucideIcon;
|
||||
children?: { title: string; link: ProLinkProps }[];
|
||||
}
|
||||
|
||||
export interface NavMainGroup {
|
||||
group: string;
|
||||
items: NavMainItem[];
|
||||
}
|
||||
|
||||
export function NavMain({
|
||||
groups,
|
||||
}: {
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface DataTablePaginationProps<TData> {
|
||||
table: Table<TData>;
|
||||
|
@ -8,7 +8,6 @@ import { type } from 'arktype';
|
||||
import {
|
||||
MikanSubscriptionSeasonSourceUrlSchema,
|
||||
extractMikanSubscriptionBangumiSourceUrl,
|
||||
extractMikanSubscriptionSeasonSourceUrl,
|
||||
extractMikanSubscriptionSubscriberSourceUrl,
|
||||
} from './mikan';
|
||||
|
||||
|
28
apps/webui/src/domains/recorder/schema/tasks.ts
Normal file
28
apps/webui/src/domains/recorder/schema/tasks.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_TASKS = gql`
|
||||
query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {
|
||||
subscriberTasks(
|
||||
pagination: $pagination
|
||||
filters: $filters
|
||||
orderBy: $orderBy
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
status,
|
||||
attempts,
|
||||
maxAttempts,
|
||||
runAt,
|
||||
lastError,
|
||||
lockAt,
|
||||
lockBy,
|
||||
doneAt,
|
||||
priority
|
||||
}
|
||||
paginationInfo {
|
||||
total
|
||||
pages
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
@ -28,6 +28,7 @@ type Documents = {
|
||||
"\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsIncrementalDocument,
|
||||
"\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsFullDocument,
|
||||
"\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionSourcesDocument,
|
||||
"\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": typeof types.GetTasksDocument,
|
||||
};
|
||||
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,
|
||||
@ -44,6 +45,7 @@ const documents: Documents = {
|
||||
"\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsIncrementalDocument,
|
||||
"\n mutation SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsFullDocument,
|
||||
"\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionSourcesDocument,
|
||||
"\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": types.GetTasksDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -116,6 +118,10 @@ export function gql(source: "\n mutation SyncSubscriptionFeedsFull($id: Int!) {
|
||||
* 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"];
|
||||
/**
|
||||
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function gql(source: "\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n"): (typeof documents)["\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n"];
|
||||
|
||||
export function gql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
|
@ -1759,6 +1759,15 @@ export type SyncSubscriptionSourcesMutationVariables = Exact<{
|
||||
|
||||
export type SyncSubscriptionSourcesMutation = { __typename?: 'Mutation', subscriptionSyncOneSources: { __typename?: 'SyncOneSubscriptionInfo', taskId: string } };
|
||||
|
||||
export type GetTasksQueryVariables = Exact<{
|
||||
filters: SubscriberTasksFilterInput;
|
||||
orderBy: SubscriberTasksOrderInput;
|
||||
pagination: PaginationInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetTasksQuery = { __typename?: 'Query', subscriberTasks: { __typename?: 'SubscriberTasksConnection', nodes: Array<{ __typename?: 'SubscriberTasks', id: string, status: string, attempts: number, maxAttempts: number, runAt: string, lastError?: string | null, lockAt?: string | null, lockBy?: string | null, doneAt?: string | null, priority: number }>, paginationInfo?: { __typename?: 'PaginationInfo', total: number, pages: number } | null } };
|
||||
|
||||
|
||||
export 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<GetCredential3rdQuery, GetCredential3rdQueryVariables>;
|
||||
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<InsertCredential3rdMutation, InsertCredential3rdMutationVariables>;
|
||||
@ -1773,4 +1782,5 @@ export const DeleteSubscriptionsDocument = {"kind":"Document","definitions":[{"k
|
||||
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<GetSubscriptionDetailQuery, GetSubscriptionDetailQueryVariables>;
|
||||
export const SyncSubscriptionFeedsIncrementalDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionFeedsIncremental"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"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<SyncSubscriptionFeedsIncrementalMutation, SyncSubscriptionFeedsIncrementalMutationVariables>;
|
||||
export const SyncSubscriptionFeedsFullDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionFeedsFull"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"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<SyncSubscriptionFeedsFullMutation, SyncSubscriptionFeedsFullMutationVariables>;
|
||||
export const SyncSubscriptionSourcesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncSubscriptionSources"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"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<SyncSubscriptionSourcesMutation, SyncSubscriptionSourcesMutationVariables>;
|
||||
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<SyncSubscriptionSourcesMutation, SyncSubscriptionSourcesMutationVariables>;
|
||||
export const GetTasksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTasks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksFilterInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksOrderInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}},{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"attempts"}},{"kind":"Field","name":{"kind":"Name","value":"maxAttempts"}},{"kind":"Field","name":{"kind":"Name","value":"runAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastError"}},{"kind":"Field","name":{"kind":"Name","value":"lockAt"}},{"kind":"Field","name":{"kind":"Name","value":"lockBy"}},{"kind":"Field","name":{"kind":"Name","value":"doneAt"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}}]}},{"kind":"Field","name":{"kind":"Name","value":"paginationInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}}]}}]}}]} as unknown as DocumentNode<GetTasksQuery, GetTasksQueryVariables>;
|
@ -1,118 +1,26 @@
|
||||
import type { ProLinkProps } from '@/components/ui/pro-link';
|
||||
import { type } from 'arktype';
|
||||
import {
|
||||
BookOpen,
|
||||
Folders,
|
||||
KeyRound,
|
||||
type LucideIcon,
|
||||
Settings2,
|
||||
SquareTerminal,
|
||||
Telescope,
|
||||
} from 'lucide-react';
|
||||
|
||||
export const AppNavMainData = [
|
||||
{
|
||||
group: 'Dashboard',
|
||||
items: [
|
||||
{
|
||||
title: 'Explore',
|
||||
icon: Telescope,
|
||||
children: [
|
||||
{
|
||||
title: 'Feed',
|
||||
link: {
|
||||
to: '/feed',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Explore',
|
||||
link: {
|
||||
to: '/explore',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Subscriptions',
|
||||
link: {
|
||||
to: '/subscriptions/manage',
|
||||
},
|
||||
icon: Folders,
|
||||
children: [
|
||||
{
|
||||
title: 'Manage',
|
||||
link: {
|
||||
to: '/subscriptions/manage',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Create',
|
||||
link: {
|
||||
to: '/subscriptions/create',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Credential',
|
||||
link: {
|
||||
to: '/credential3rd/manage',
|
||||
},
|
||||
icon: KeyRound,
|
||||
children: [
|
||||
{
|
||||
title: 'Manage',
|
||||
link: {
|
||||
to: '/credential3rd/manage',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Create',
|
||||
link: {
|
||||
to: '/credential3rd/create',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Playground',
|
||||
icon: SquareTerminal,
|
||||
link: {
|
||||
to: '/playground',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: 'GraphQL Api',
|
||||
link: {
|
||||
to: '/playground/graphql-api',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
link: {
|
||||
href: 'https://github.com/dumtruck/konobangu/wiki',
|
||||
target: '_blank',
|
||||
},
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
link: {
|
||||
to: '/settings',
|
||||
},
|
||||
icon: Settings2,
|
||||
children: [
|
||||
{
|
||||
title: 'Downloader',
|
||||
link: {
|
||||
to: '/settings/downloader',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
export interface NavMainItem {
|
||||
link?: ProLinkProps;
|
||||
title: string;
|
||||
icon?: LucideIcon;
|
||||
children?: { title: string; link: ProLinkProps }[];
|
||||
}
|
||||
|
||||
export interface NavMainGroup {
|
||||
group: string;
|
||||
items: NavMainItem[];
|
||||
}
|
||||
|
||||
export const CreateCompleteAction = {
|
||||
Back: 'back',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useStateRef } from './use-state-ref.ts';
|
||||
import { useStateRef } from './use-state-ref';
|
||||
export interface UseDebouncedSkeletonProps {
|
||||
minSkeletonDuration?: number;
|
||||
loading?: boolean;
|
||||
|
@ -32,7 +32,6 @@ import { Route as AppPlaygroundGraphqlApiImport } from './routes/_app/playground
|
||||
import { Route as AppCredential3rdManageImport } from './routes/_app/credential3rd/manage'
|
||||
import { Route as AppCredential3rdCreateImport } from './routes/_app/credential3rd/create'
|
||||
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'
|
||||
@ -169,12 +168,6 @@ const AppBangumiManageRoute = AppBangumiManageImport.update({
|
||||
getParentRoute: () => AppBangumiRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppExploreFeedRoute = AppExploreFeedImport.update({
|
||||
id: '/_explore/feed',
|
||||
path: '/feed',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppExploreExploreRoute = AppExploreExploreImport.update({
|
||||
id: '/_explore/explore',
|
||||
path: '/explore',
|
||||
@ -306,13 +299,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AppExploreExploreImport
|
||||
parentRoute: typeof AppRouteImport
|
||||
}
|
||||
'/_app/_explore/feed': {
|
||||
id: '/_app/_explore/feed'
|
||||
path: '/feed'
|
||||
fullPath: '/feed'
|
||||
preLoaderRoute: typeof AppExploreFeedImport
|
||||
parentRoute: typeof AppRouteImport
|
||||
}
|
||||
'/_app/bangumi/manage': {
|
||||
id: '/_app/bangumi/manage'
|
||||
path: '/manage'
|
||||
@ -510,7 +496,6 @@ interface AppRouteRouteChildren {
|
||||
AppSubscriptionsRouteRoute: typeof AppSubscriptionsRouteRouteWithChildren
|
||||
AppTasksRouteRoute: typeof AppTasksRouteRouteWithChildren
|
||||
AppExploreExploreRoute: typeof AppExploreExploreRoute
|
||||
AppExploreFeedRoute: typeof AppExploreFeedRoute
|
||||
}
|
||||
|
||||
const AppRouteRouteChildren: AppRouteRouteChildren = {
|
||||
@ -521,7 +506,6 @@ const AppRouteRouteChildren: AppRouteRouteChildren = {
|
||||
AppSubscriptionsRouteRoute: AppSubscriptionsRouteRouteWithChildren,
|
||||
AppTasksRouteRoute: AppTasksRouteRouteWithChildren,
|
||||
AppExploreExploreRoute: AppExploreExploreRoute,
|
||||
AppExploreFeedRoute: AppExploreFeedRoute,
|
||||
}
|
||||
|
||||
const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(
|
||||
@ -542,7 +526,6 @@ export interface FileRoutesByFullPath {
|
||||
'/auth/sign-in': typeof AuthSignInRoute
|
||||
'/auth/sign-up': typeof AuthSignUpRoute
|
||||
'/explore': typeof AppExploreExploreRoute
|
||||
'/feed': typeof AppExploreFeedRoute
|
||||
'/bangumi/manage': typeof AppBangumiManageRoute
|
||||
'/credential3rd/create': typeof AppCredential3rdCreateRoute
|
||||
'/credential3rd/manage': typeof AppCredential3rdManageRoute
|
||||
@ -573,7 +556,6 @@ export interface FileRoutesByTo {
|
||||
'/auth/sign-in': typeof AuthSignInRoute
|
||||
'/auth/sign-up': typeof AuthSignUpRoute
|
||||
'/explore': typeof AppExploreExploreRoute
|
||||
'/feed': typeof AppExploreFeedRoute
|
||||
'/bangumi/manage': typeof AppBangumiManageRoute
|
||||
'/credential3rd/create': typeof AppCredential3rdCreateRoute
|
||||
'/credential3rd/manage': typeof AppCredential3rdManageRoute
|
||||
@ -605,7 +587,6 @@ export interface FileRoutesById {
|
||||
'/auth/sign-in': typeof AuthSignInRoute
|
||||
'/auth/sign-up': typeof AuthSignUpRoute
|
||||
'/_app/_explore/explore': typeof AppExploreExploreRoute
|
||||
'/_app/_explore/feed': typeof AppExploreFeedRoute
|
||||
'/_app/bangumi/manage': typeof AppBangumiManageRoute
|
||||
'/_app/credential3rd/create': typeof AppCredential3rdCreateRoute
|
||||
'/_app/credential3rd/manage': typeof AppCredential3rdManageRoute
|
||||
@ -638,7 +619,6 @@ export interface FileRouteTypes {
|
||||
| '/auth/sign-in'
|
||||
| '/auth/sign-up'
|
||||
| '/explore'
|
||||
| '/feed'
|
||||
| '/bangumi/manage'
|
||||
| '/credential3rd/create'
|
||||
| '/credential3rd/manage'
|
||||
@ -668,7 +648,6 @@ export interface FileRouteTypes {
|
||||
| '/auth/sign-in'
|
||||
| '/auth/sign-up'
|
||||
| '/explore'
|
||||
| '/feed'
|
||||
| '/bangumi/manage'
|
||||
| '/credential3rd/create'
|
||||
| '/credential3rd/manage'
|
||||
@ -698,7 +677,6 @@ export interface FileRouteTypes {
|
||||
| '/auth/sign-in'
|
||||
| '/auth/sign-up'
|
||||
| '/_app/_explore/explore'
|
||||
| '/_app/_explore/feed'
|
||||
| '/_app/bangumi/manage'
|
||||
| '/_app/credential3rd/create'
|
||||
| '/_app/credential3rd/manage'
|
||||
@ -767,8 +745,7 @@ export const routeTree = rootRoute
|
||||
"/_app/settings",
|
||||
"/_app/subscriptions",
|
||||
"/_app/tasks",
|
||||
"/_app/_explore/explore",
|
||||
"/_app/_explore/feed"
|
||||
"/_app/_explore/explore"
|
||||
]
|
||||
},
|
||||
"/404": {
|
||||
@ -836,10 +813,6 @@ export const routeTree = rootRoute
|
||||
"filePath": "_app/_explore/explore.tsx",
|
||||
"parent": "/_app"
|
||||
},
|
||||
"/_app/_explore/feed": {
|
||||
"filePath": "_app/_explore/feed.tsx",
|
||||
"parent": "/_app"
|
||||
},
|
||||
"/_app/bangumi/manage": {
|
||||
"filePath": "_app/bangumi/manage.tsx",
|
||||
"parent": "/_app/bangumi"
|
||||
|
@ -1,15 +0,0 @@
|
||||
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/_app/_explore/feed')({
|
||||
component: FeedRouteComponent,
|
||||
staticData: {
|
||||
breadcrumb: {
|
||||
label: 'Feed',
|
||||
},
|
||||
} satisfies RouteStateDataOption,
|
||||
});
|
||||
|
||||
function FeedRouteComponent() {
|
||||
return <div>Hello "/_app/feed"!</div>;
|
||||
}
|
@ -269,7 +269,7 @@ function CredentialManageRouteComponent() {
|
||||
}, [handleDeleteRecord, navigate, showPasswords, togglePasswordVisibility]);
|
||||
|
||||
const table = useReactTable({
|
||||
data: data?.credential3rd?.nodes ?? [],
|
||||
data: useMemo(() => credentials?.nodes ?? [], [credentials]),
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
@ -335,7 +335,7 @@ function CredentialManageRouteComponent() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{showSkeleton &&
|
||||
Array.from(new Array(pagination.pageSize)).map((_, index) => (
|
||||
Array.from(new Array(10)).map((_, index) => (
|
||||
<TableRow key={index}>
|
||||
{table.getVisibleLeafColumns().map((column) => (
|
||||
<TableCell key={column.id}>
|
||||
|
@ -220,13 +220,14 @@ function SubscriptionDetailRouteComponent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{subscription.credential3rd && (
|
||||
{subscription.category ===
|
||||
SubscriptionCategoryEnum.MikanSeason && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium text-sm">Credential ID</Label>
|
||||
<div className="rounded-md bg-muted p-3">
|
||||
<code className="text-sm">
|
||||
{subscription.credential3rd.id}
|
||||
{subscription.credential3rd?.id ?? '-'}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
@ -237,7 +238,7 @@ function SubscriptionDetailRouteComponent() {
|
||||
</Label>
|
||||
<div className="rounded-md bg-muted p-3">
|
||||
<code className="text-sm">
|
||||
{subscription.credential3rd.username}
|
||||
{subscription.credential3rd?.username ?? '-'}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,6 +90,7 @@ function SubscriptionManageRouteComponent() {
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const [updateSubscription] = useMutation(UPDATE_SUBSCRIPTIONS, {
|
||||
onCompleted: async () => {
|
||||
const refetchResult = await refetch();
|
||||
@ -260,7 +261,7 @@ function SubscriptionManageRouteComponent() {
|
||||
}, [handleUpdateRecord, handleDeleteRecord, navigate]);
|
||||
|
||||
const table = useReactTable({
|
||||
data: data?.subscriptions?.nodes ?? [],
|
||||
data: useMemo(() => subscriptions?.nodes ?? [], [subscriptions]),
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
@ -270,6 +271,8 @@ function SubscriptionManageRouteComponent() {
|
||||
pageCount: subscriptions?.paginationInfo?.pages,
|
||||
rowCount: subscriptions?.paginationInfo?.total,
|
||||
enableColumnPinning: true,
|
||||
autoResetPageIndex: true,
|
||||
manualPagination: true,
|
||||
state: {
|
||||
pagination,
|
||||
sorting,
|
||||
@ -323,7 +326,7 @@ function SubscriptionManageRouteComponent() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{showSkeleton &&
|
||||
Array.from(new Array(pagination.pageSize)).map((_, index) => (
|
||||
Array.from(new Array(10)).map((_, index) => (
|
||||
<TableRow key={index}>
|
||||
{table.getVisibleLeafColumns().map((column) => (
|
||||
<TableCell key={column.id}>
|
||||
|
Loading…
Reference in New Issue
Block a user