feat: add tasks manage view
This commit is contained in:
parent
258eeddc74
commit
07955286f1
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -39,5 +39,13 @@
|
|||||||
"username": "konobangu"
|
"username": "konobangu"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rust-analyzer.cargo.features": "all"
|
"rust-analyzer.cargo.features": "all",
|
||||||
|
// https://github.com/rust-lang/rust/issues/141540
|
||||||
|
"rust-analyzer.cargo.targetDir": "target/rust-analyzer",
|
||||||
|
"rust-analyzer.check.extraEnv": {
|
||||||
|
"CARGO_TARGET_DIR": "target/rust-analyzer"
|
||||||
|
},
|
||||||
|
"rust-analyzer.cargo.extraEnv": {
|
||||||
|
"CARGO_TARGET_DIR": "target/analyzer"
|
||||||
|
}
|
||||||
}
|
}
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -5995,7 +5995,7 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "seaography"
|
name = "seaography"
|
||||||
version = "1.1.4"
|
version = "1.1.4"
|
||||||
source = "git+https://github.com/dumtruck/seaography.git?rev=10ba248#10ba2487fb356a0385c598290668a01e0ef21734"
|
source = "git+https://github.com/dumtruck/seaography.git?rev=01d3f99#01d3f99aebd476860b9c061676b2b22083188b03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-graphql",
|
"async-graphql",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -67,4 +67,4 @@ color-eyre = "0.6.5"
|
|||||||
inquire = "0.7.5"
|
inquire = "0.7.5"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
seaography = { git = "https://github.com/dumtruck/seaography.git", rev = "10ba248" }
|
seaography = { git = "https://github.com/dumtruck/seaography.git", rev = "01d3f99" }
|
||||||
|
@ -87,6 +87,7 @@ seaography = { version = "1.1", features = [
|
|||||||
"with-decimal",
|
"with-decimal",
|
||||||
"with-bigdecimal",
|
"with-bigdecimal",
|
||||||
"with-postgres-array",
|
"with-postgres-array",
|
||||||
|
"with-json-as-scalar",
|
||||||
] }
|
] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
|
@ -142,7 +142,7 @@ async fn sync_mikan_feeds_from_rss_item_list(
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct MikanSubscriberSubscription {
|
pub struct MikanSubscriberSubscription {
|
||||||
pub id: i32,
|
pub subscription_id: i32,
|
||||||
pub mikan_subscription_token: String,
|
pub mikan_subscription_token: String,
|
||||||
pub subscriber_id: i32,
|
pub subscriber_id: i32,
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@ impl SubscriptionTrait for MikanSubscriberSubscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_subscription_id(&self) -> i32 {
|
fn get_subscription_id(&self) -> i32 {
|
||||||
self.id
|
self.subscription_id
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
||||||
@ -204,7 +204,7 @@ impl SubscriptionTrait for MikanSubscriberSubscription {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: model.id,
|
subscription_id: model.id,
|
||||||
mikan_subscription_token: meta.mikan_subscription_token,
|
mikan_subscription_token: meta.mikan_subscription_token,
|
||||||
subscriber_id: model.subscriber_id,
|
subscriber_id: model.subscriber_id,
|
||||||
})
|
})
|
||||||
@ -243,7 +243,8 @@ impl MikanSubscriberSubscription {
|
|||||||
ctx: &dyn AppContextTrait,
|
ctx: &dyn AppContextTrait,
|
||||||
) -> RecorderResult<Vec<MikanRssEpisodeItem>> {
|
) -> RecorderResult<Vec<MikanRssEpisodeItem>> {
|
||||||
let subscribed_bangumi_list =
|
let subscribed_bangumi_list =
|
||||||
bangumi::Model::get_subsribed_bangumi_list_from_subscription(ctx, self.id).await?;
|
bangumi::Model::get_subsribed_bangumi_list_from_subscription(ctx, self.subscription_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut rss_item_list = vec![];
|
let mut rss_item_list = vec![];
|
||||||
for subscribed_bangumi in subscribed_bangumi_list {
|
for subscribed_bangumi in subscribed_bangumi_list {
|
||||||
@ -252,7 +253,7 @@ impl MikanSubscriberSubscription {
|
|||||||
.with_whatever_context::<_, String, RecorderError>(|| {
|
.with_whatever_context::<_, String, RecorderError>(|| {
|
||||||
format!(
|
format!(
|
||||||
"rss link is required, subscription_id = {:?}, bangumi_name = {}",
|
"rss link is required, subscription_id = {:?}, bangumi_name = {}",
|
||||||
self.id, subscribed_bangumi.display_name
|
self.subscription_id, subscribed_bangumi.display_name
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let bytes = fetch_bytes(ctx.mikan(), rss_url).await?;
|
let bytes = fetch_bytes(ctx.mikan(), rss_url).await?;
|
||||||
@ -273,7 +274,7 @@ impl MikanSubscriberSubscription {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, InputObject, SimpleObject)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, InputObject, SimpleObject)]
|
||||||
pub struct MikanSeasonSubscription {
|
pub struct MikanSeasonSubscription {
|
||||||
pub id: i32,
|
pub subscription_id: i32,
|
||||||
pub year: i32,
|
pub year: i32,
|
||||||
pub season_str: MikanSeasonStr,
|
pub season_str: MikanSeasonStr,
|
||||||
pub credential_id: i32,
|
pub credential_id: i32,
|
||||||
@ -287,7 +288,7 @@ impl SubscriptionTrait for MikanSeasonSubscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_subscription_id(&self) -> i32 {
|
fn get_subscription_id(&self) -> i32 {
|
||||||
self.id
|
self.subscription_id
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
||||||
@ -363,7 +364,7 @@ impl SubscriptionTrait for MikanSeasonSubscription {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: model.id,
|
subscription_id: model.id,
|
||||||
year: source_url_meta.year,
|
year: source_url_meta.year,
|
||||||
season_str: source_url_meta.season_str,
|
season_str: source_url_meta.season_str,
|
||||||
credential_id,
|
credential_id,
|
||||||
@ -400,7 +401,10 @@ impl MikanSeasonSubscription {
|
|||||||
let db = ctx.db();
|
let db = ctx.db();
|
||||||
|
|
||||||
let subscribed_bangumi_list = bangumi::Entity::find()
|
let subscribed_bangumi_list = bangumi::Entity::find()
|
||||||
.filter(Condition::all().add(subscription_bangumi::Column::SubscriptionId.eq(self.id)))
|
.filter(
|
||||||
|
Condition::all()
|
||||||
|
.add(subscription_bangumi::Column::SubscriptionId.eq(self.subscription_id)),
|
||||||
|
)
|
||||||
.join_rev(
|
.join_rev(
|
||||||
JoinType::InnerJoin,
|
JoinType::InnerJoin,
|
||||||
subscription_bangumi::Relation::Bangumi.def(),
|
subscription_bangumi::Relation::Bangumi.def(),
|
||||||
@ -415,7 +419,7 @@ impl MikanSeasonSubscription {
|
|||||||
.with_whatever_context::<_, String, RecorderError>(|| {
|
.with_whatever_context::<_, String, RecorderError>(|| {
|
||||||
format!(
|
format!(
|
||||||
"rss_link is required, subscription_id = {}, bangumi_name = {}",
|
"rss_link is required, subscription_id = {}, bangumi_name = {}",
|
||||||
self.id, subscribed_bangumi.display_name
|
self.subscription_id, subscribed_bangumi.display_name
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let bytes = fetch_bytes(ctx.mikan(), rss_url).await?;
|
let bytes = fetch_bytes(ctx.mikan(), rss_url).await?;
|
||||||
@ -436,7 +440,7 @@ impl MikanSeasonSubscription {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, InputObject, SimpleObject)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, InputObject, SimpleObject)]
|
||||||
pub struct MikanBangumiSubscription {
|
pub struct MikanBangumiSubscription {
|
||||||
pub id: i32,
|
pub subscription_id: i32,
|
||||||
pub mikan_bangumi_id: String,
|
pub mikan_bangumi_id: String,
|
||||||
pub mikan_fansub_id: String,
|
pub mikan_fansub_id: String,
|
||||||
pub subscriber_id: i32,
|
pub subscriber_id: i32,
|
||||||
@ -449,7 +453,7 @@ impl SubscriptionTrait for MikanBangumiSubscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_subscription_id(&self) -> i32 {
|
fn get_subscription_id(&self) -> i32 {
|
||||||
self.id
|
self.subscription_id
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
||||||
@ -487,7 +491,7 @@ impl SubscriptionTrait for MikanBangumiSubscription {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: model.id,
|
subscription_id: model.id,
|
||||||
mikan_bangumi_id: meta.mikan_bangumi_id,
|
mikan_bangumi_id: meta.mikan_bangumi_id,
|
||||||
mikan_fansub_id: meta.mikan_fansub_id,
|
mikan_fansub_id: meta.mikan_fansub_id,
|
||||||
subscriber_id: model.subscriber_id,
|
subscriber_id: model.subscriber_id,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_graphql::dynamic::ValueAccessor;
|
use async_graphql::dynamic::{ResolverContext, ValueAccessor};
|
||||||
use sea_orm::{EntityTrait, Value as SeaValue};
|
use sea_orm::{EntityTrait, Value as SeaValue};
|
||||||
use seaography::{BuilderContext, SeaResult};
|
use seaography::{BuilderContext, SeaResult};
|
||||||
|
|
||||||
@ -25,11 +25,15 @@ fn register_crypto_column_input_conversion_to_schema_context<T>(
|
|||||||
|
|
||||||
context.types.input_conversions.insert(
|
context.types.input_conversions.insert(
|
||||||
format!("{entity_name}.{column_name}"),
|
format!("{entity_name}.{column_name}"),
|
||||||
Box::new(move |value: &ValueAccessor| -> SeaResult<sea_orm::Value> {
|
Box::new(
|
||||||
let source = value.string()?;
|
move |_resolve_context: &ResolverContext<'_>,
|
||||||
let encrypted = ctx.crypto().encrypt_string(source.into())?;
|
value: &ValueAccessor|
|
||||||
Ok(encrypted.into())
|
-> SeaResult<sea_orm::Value> {
|
||||||
}),
|
let source = value.string()?;
|
||||||
|
let encrypted = ctx.crypto().encrypt_string(source.into())?;
|
||||||
|
Ok(encrypted.into())
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,10 @@
|
|||||||
use async_graphql::dynamic::Scalar;
|
use seaography::{Builder as SeaographyBuilder, BuilderContext};
|
||||||
use seaography::{Builder as SeaographyBuilder, BuilderContext, ConvertedType};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
graphql::infra::{
|
graphql::infra::json::restrict_jsonb_filter_input_for_entity, models::subscriber_tasks,
|
||||||
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) {
|
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>(
|
restrict_jsonb_filter_input_for_entity::<subscriber_tasks::Entity>(
|
||||||
context,
|
context,
|
||||||
&subscriber_tasks::Column::Job,
|
&subscriber_tasks::Column::Job,
|
||||||
@ -27,16 +14,6 @@ pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext)
|
|||||||
pub fn register_subscriber_tasks_to_schema_builder(
|
pub fn register_subscriber_tasks_to_schema_builder(
|
||||||
mut builder: SeaographyBuilder,
|
mut builder: SeaographyBuilder,
|
||||||
) -> SeaographyBuilder {
|
) -> SeaographyBuilder {
|
||||||
let subscriber_tasks_scalar = Scalar::new("SubscriberTasks")
|
builder.register_enumeration::<subscriber_tasks::SubscriberTaskType>();
|
||||||
.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
|
builder
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,29 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_graphql::dynamic::{ResolverContext, ValueAccessor};
|
use async_graphql::dynamic::{ObjectAccessor, ResolverContext, TypeRef, ValueAccessor};
|
||||||
use sea_orm::EntityTrait;
|
use lazy_static::lazy_static;
|
||||||
use seaography::{BuilderContext, FnGuard, GuardAction};
|
use maplit::btreeset;
|
||||||
|
use sea_orm::{ColumnTrait, Condition, EntityTrait, Iterable, Value as SeaValue};
|
||||||
|
use seaography::{
|
||||||
|
Builder as SeaographyBuilder, BuilderContext, FilterInfo,
|
||||||
|
FilterOperation as SeaographqlFilterOperation, FilterType, FilterTypesMapHelper,
|
||||||
|
FnFilterCondition, FnGuard, FnInputTypeNoneConversion, GuardAction, SeaResult, SeaographyError,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{AuthError, AuthUserInfo},
|
auth::{AuthError, AuthUserInfo},
|
||||||
graphql::infra::util::{get_column_key, get_entity_key},
|
graphql::infra::util::{get_column_key, get_entity_column_key, get_entity_key},
|
||||||
|
models::subscribers,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn guard_data_object_accessor_with_subscriber_id(
|
fn guard_data_object_accessor_with_subscriber_id(
|
||||||
value: ValueAccessor<'_>,
|
value: ValueAccessor<'_>,
|
||||||
column_name: &str,
|
column_name: &str,
|
||||||
@ -181,3 +196,161 @@ where
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_subscriber_id_filter_condition<T>(
|
||||||
|
_context: &BuilderContext,
|
||||||
|
column: &T::Column,
|
||||||
|
) -> FnFilterCondition
|
||||||
|
where
|
||||||
|
T: EntityTrait,
|
||||||
|
<T as EntityTrait>::Model: Sync,
|
||||||
|
{
|
||||||
|
let column = *column;
|
||||||
|
Box::new(
|
||||||
|
move |context: &ResolverContext,
|
||||||
|
mut condition: Condition,
|
||||||
|
filter: Option<&ObjectAccessor<'_>>|
|
||||||
|
-> SeaResult<Condition> {
|
||||||
|
match context.ctx.data::<AuthUserInfo>() {
|
||||||
|
Ok(user_info) => {
|
||||||
|
let subscriber_id = user_info.subscriber_auth.subscriber_id;
|
||||||
|
|
||||||
|
if let Some(filter) = filter {
|
||||||
|
for operation in &SUBSCRIBER_ID_FILTER_INFO.supported_operations {
|
||||||
|
match operation {
|
||||||
|
SeaographqlFilterOperation::Equals => {
|
||||||
|
if let Some(value) = filter.get("eq") {
|
||||||
|
let value: i32 = value.i64()?.try_into()?;
|
||||||
|
if value != subscriber_id {
|
||||||
|
return Err(SeaographyError::AsyncGraphQLError(
|
||||||
|
async_graphql::Error::new(
|
||||||
|
"subscriber_id and auth_info does not match",
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!("unreachable filter operation for subscriber_id"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
condition = condition.add(column.eq(subscriber_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(condition)
|
||||||
|
}
|
||||||
|
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_default_subscriber_id_input_conversion<T>(
|
||||||
|
context: &BuilderContext,
|
||||||
|
_column: &T::Column,
|
||||||
|
) -> FnInputTypeNoneConversion
|
||||||
|
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 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| -> SeaResult<Option<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;
|
||||||
|
Ok(Some(SeaValue::Int(Some(subscriber_id))))
|
||||||
|
}
|
||||||
|
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &entity_column_key);
|
||||||
|
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_filter_condition::<T>(context, column),
|
||||||
|
);
|
||||||
|
context.types.input_none_conversions.insert(
|
||||||
|
column_name.clone(),
|
||||||
|
generate_default_subscriber_id_input_conversion::<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
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
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};
|
|
||||||
|
|
||||||
use crate::graphql::infra::filter::FnFilterCondition;
|
|
||||||
|
|
||||||
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 generate_subscriber_id_condition_function<T>(
|
|
||||||
_context: &BuilderContext,
|
|
||||||
column: &T::Column,
|
|
||||||
) -> FnFilterCondition
|
|
||||||
where
|
|
||||||
T: EntityTrait,
|
|
||||||
<T as EntityTrait>::Model: Sync,
|
|
||||||
{
|
|
||||||
let column = *column;
|
|
||||||
Box::new(move |mut condition, filter| {
|
|
||||||
for operation in &SUBSCRIBER_ID_FILTER_INFO.supported_operations {
|
|
||||||
match operation {
|
|
||||||
SeaographqlFilterOperation::Equals => {
|
|
||||||
if let Some(value) = filter.get("eq") {
|
|
||||||
let value: i32 = value.i64()?.try_into()?;
|
|
||||||
let value = sea_orm::Value::Int(Some(value));
|
|
||||||
condition = condition.add(column.eq(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!("unreachable filter operation for subscriber_id"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(condition)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
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,6 +1,6 @@
|
|||||||
use async_graphql::{
|
use async_graphql::{
|
||||||
Error as GraphqlError,
|
Error as GraphqlError,
|
||||||
dynamic::{Scalar, SchemaError},
|
dynamic::{ResolverContext, Scalar, SchemaError},
|
||||||
to_value,
|
to_value,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -9,13 +9,12 @@ use sea_orm::{
|
|||||||
Condition, EntityTrait,
|
Condition, EntityTrait,
|
||||||
sea_query::{ArrayType, Expr, ExprTrait, IntoLikeExpr, SimpleExpr, Value as DbValue},
|
sea_query::{ArrayType, Expr, ExprTrait, IntoLikeExpr, SimpleExpr, Value as DbValue},
|
||||||
};
|
};
|
||||||
use seaography::{Builder as SeaographyBuilder, BuilderContext, FilterType, SeaographyError};
|
use seaography::{
|
||||||
|
Builder as SeaographyBuilder, BuilderContext, FilterType, FnFilterCondition, SeaographyError,
|
||||||
|
};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{errors::RecorderResult, graphql::infra::util::get_entity_column_key};
|
||||||
errors::RecorderResult,
|
|
||||||
graphql::infra::{filter::FnFilterCondition, util::get_entity_column_key},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||||
pub enum JsonbFilterOperation {
|
pub enum JsonbFilterOperation {
|
||||||
@ -904,20 +903,29 @@ where
|
|||||||
<T as EntityTrait>::Model: Sync,
|
<T as EntityTrait>::Model: Sync,
|
||||||
{
|
{
|
||||||
let column = *column;
|
let column = *column;
|
||||||
Box::new(move |mut condition, filter| {
|
Box::new(
|
||||||
let filter_value = to_value(filter.as_index_map())
|
move |_resolve_context: &ResolverContext<'_>, condition, filter| {
|
||||||
.map_err(|e| SeaographyError::AsyncGraphQLError(GraphqlError::new_with_source(e)))?;
|
if let Some(filter) = filter {
|
||||||
|
let filter_value = to_value(filter.as_index_map()).map_err(|e| {
|
||||||
|
SeaographyError::AsyncGraphQLError(GraphqlError::new_with_source(e))
|
||||||
|
})?;
|
||||||
|
|
||||||
let filter_json: JsonValue = filter_value
|
let filter_json: JsonValue = filter_value.into_json().map_err(|e| {
|
||||||
.into_json()
|
SeaographyError::AsyncGraphQLError(GraphqlError::new(format!("{e:?}")))
|
||||||
.map_err(|e| SeaographyError::AsyncGraphQLError(GraphqlError::new(format!("{e:?}"))))?;
|
})?;
|
||||||
|
|
||||||
let cond_where = prepare_jsonb_filter_input(&Expr::col(column), filter_json)
|
let cond_where = prepare_jsonb_filter_input(&Expr::col(column), filter_json)
|
||||||
.map_err(|e| SeaographyError::AsyncGraphQLError(GraphqlError::new_with_source(e)))?;
|
.map_err(|e| {
|
||||||
|
SeaographyError::AsyncGraphQLError(GraphqlError::new_with_source(e))
|
||||||
|
})?;
|
||||||
|
|
||||||
condition = condition.add(cond_where);
|
let condition = condition.add(cond_where);
|
||||||
Ok(condition)
|
Ok(condition)
|
||||||
})
|
} else {
|
||||||
|
Ok(condition)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_jsonb_input_filter_to_schema_builder(
|
pub fn register_jsonb_input_filter_to_schema_builder(
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
pub mod json;
|
pub mod json;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod filter;
|
|
||||||
|
@ -12,13 +12,13 @@ impl MigrationTrait for Migration {
|
|||||||
let db = manager.get_connection();
|
let db = manager.get_connection();
|
||||||
|
|
||||||
db.execute_unprepared(&format!(
|
db.execute_unprepared(&format!(
|
||||||
r#"CREATE OR REPLACE VIEW subscriber_task AS
|
r#"CREATE OR REPLACE VIEW subscriber_tasks AS
|
||||||
SELECT
|
SELECT
|
||||||
job,
|
job,
|
||||||
job_type,
|
job_type,
|
||||||
status,
|
status,
|
||||||
(job->'subscriber_id')::integer AS subscriber_id,
|
(job ->> 'subscriber_id'::text)::integer AS subscriber_id,
|
||||||
(job->'task_type')::text AS task_type,
|
job ->> 'task_type'::text AS task_type,
|
||||||
id,
|
id,
|
||||||
attempts,
|
attempts,
|
||||||
max_attempts,
|
max_attempts,
|
||||||
@ -56,7 +56,7 @@ AND jsonb_path_exists(job, '$.task_type ? (@.type() == "string")')"#,
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
db.execute_unprepared("DROP VIEW IF EXISTS subscriber_task")
|
db.execute_unprepared("DROP VIEW IF EXISTS subscriber_tasks")
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
pub use crate::task::SubscriberTask;
|
pub use crate::task::{SubscriberTask, SubscriberTaskType};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
#[sea_orm(table_name = "subscriber_tasks")]
|
#[sea_orm(table_name = "subscriber_tasks")]
|
||||||
@ -9,6 +10,7 @@ pub struct Model {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub subscriber_id: i32,
|
pub subscriber_id: i32,
|
||||||
pub job: SubscriberTask,
|
pub job: SubscriberTask,
|
||||||
|
pub task_type: SubscriberTaskType,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub attempts: i32,
|
pub attempts: i32,
|
||||||
pub max_attempts: i32,
|
pub max_attempts: i32,
|
||||||
@ -44,4 +46,5 @@ pub enum RelatedEntity {
|
|||||||
Subscriber,
|
Subscriber,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
@ -7,6 +7,8 @@ pub use core::{SUBSCRIBER_TASK_APALIS_NAME, SubscriberAsyncTaskTrait, Subscriber
|
|||||||
|
|
||||||
pub use config::TaskConfig;
|
pub use config::TaskConfig;
|
||||||
pub use registry::{
|
pub use registry::{
|
||||||
SubscriberTask, SyncOneSubscriptionFeedsIncrementalTask, SyncOneSubscriptionSourcesTask,
|
SubscriberTask, SubscriberTaskType, SubscriberTaskTypeEnum, SubscriberTaskTypeVariant,
|
||||||
|
SubscriberTaskTypeVariantIter, SyncOneSubscriptionFeedsFullTask,
|
||||||
|
SyncOneSubscriptionFeedsIncrementalTask, SyncOneSubscriptionSourcesTask,
|
||||||
};
|
};
|
||||||
pub use service::TaskService;
|
pub use service::TaskService;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
mod subscription;
|
mod subscription;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use sea_orm::FromJsonQueryResult;
|
use sea_orm::{DeriveActiveEnum, DeriveDisplay, EnumIter, FromJsonQueryResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use subscription::{
|
pub use subscription::{
|
||||||
SyncOneSubscriptionFeedsFullTask, SyncOneSubscriptionFeedsIncrementalTask,
|
SyncOneSubscriptionFeedsFullTask, SyncOneSubscriptionFeedsIncrementalTask,
|
||||||
@ -15,16 +15,28 @@ use crate::{
|
|||||||
models::subscriptions::SubscriptionTrait,
|
models::subscriptions::SubscriptionTrait,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(async_graphql::Enum, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Copy,
|
||||||
|
DeriveActiveEnum,
|
||||||
|
DeriveDisplay,
|
||||||
|
EnumIter,
|
||||||
|
)]
|
||||||
|
#[sea_orm(rs_type = "String", db_type = "Text")]
|
||||||
pub enum SubscriberTaskType {
|
pub enum SubscriberTaskType {
|
||||||
#[serde(rename = "sync_one_subscription_feeds_incremental")]
|
#[serde(rename = "sync_one_subscription_feeds_incremental")]
|
||||||
#[graphql(name = "sync_one_subscription_feeds_incremental")]
|
#[sea_orm(string_value = "sync_one_subscription_feeds_incremental")]
|
||||||
SyncOneSubscriptionFeedsIncremental,
|
SyncOneSubscriptionFeedsIncremental,
|
||||||
#[serde(rename = "sync_one_subscription_feeds_full")]
|
#[serde(rename = "sync_one_subscription_feeds_full")]
|
||||||
#[graphql(name = "sync_one_subscription_feeds_full")]
|
#[sea_orm(string_value = "sync_one_subscription_feeds_full")]
|
||||||
SyncOneSubscriptionFeedsFull,
|
SyncOneSubscriptionFeedsFull,
|
||||||
#[serde(rename = "sync_one_subscription_sources")]
|
#[serde(rename = "sync_one_subscription_sources")]
|
||||||
#[graphql(name = "sync_one_subscription_sources")]
|
#[sea_orm(string_value = "sync_one_subscription_sources")]
|
||||||
SyncOneSubscriptionSources,
|
SyncOneSubscriptionSources,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export const AppNavMainData: NavMainGroup[] = [
|
|||||||
{
|
{
|
||||||
title: 'Manage',
|
title: 'Manage',
|
||||||
link: {
|
link: {
|
||||||
to: '/task/manage',
|
to: '/tasks/manage',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -12,10 +12,12 @@ import {
|
|||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import type * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
|
|
||||||
import { PropsWithChildren, useMemo } from "react";
|
import { ComponentProps, PropsWithChildren, useMemo } from "react";
|
||||||
|
|
||||||
interface DataTableRowActionsProps<DataView, Id> {
|
interface DataTableRowActionsProps<DataView, Id>
|
||||||
|
extends ComponentProps<typeof DropdownMenuPrimitive.Root> {
|
||||||
row: Row<DataView>;
|
row: Row<DataView>;
|
||||||
getId: (row: Row<DataView>) => Id;
|
getId: (row: Row<DataView>) => Id;
|
||||||
showDetail?: boolean;
|
showDetail?: boolean;
|
||||||
@ -24,7 +26,6 @@ interface DataTableRowActionsProps<DataView, Id> {
|
|||||||
onDetail?: (id: Id) => void;
|
onDetail?: (id: Id) => void;
|
||||||
onDelete?: (id: Id) => void;
|
onDelete?: (id: Id) => void;
|
||||||
onEdit?: (id: Id) => void;
|
onEdit?: (id: Id) => void;
|
||||||
modal?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTableRowActions<DataView, Id>({
|
export function DataTableRowActions<DataView, Id>({
|
||||||
@ -37,11 +38,11 @@ export function DataTableRowActions<DataView, Id>({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onEdit,
|
onEdit,
|
||||||
children,
|
children,
|
||||||
modal,
|
...rest
|
||||||
}: PropsWithChildren<DataTableRowActionsProps<DataView, Id>>) {
|
}: PropsWithChildren<DataTableRowActionsProps<DataView, Id>>) {
|
||||||
const id = useMemo(() => getId(row), [getId, row]);
|
const id = useMemo(() => getId(row), [getId, row]);
|
||||||
return (
|
return (
|
||||||
<DropdownMenu modal={modal}>
|
<DropdownMenu {...rest}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
@ -6,7 +6,9 @@ import {
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
import { type } from 'arktype';
|
import { type } from 'arktype';
|
||||||
import {
|
import {
|
||||||
|
MikanSubscriptionBangumiSourceUrlSchema,
|
||||||
MikanSubscriptionSeasonSourceUrlSchema,
|
MikanSubscriptionSeasonSourceUrlSchema,
|
||||||
|
MikanSubscriptionSubscriberSourceUrlSchema,
|
||||||
extractMikanSubscriptionBangumiSourceUrl,
|
extractMikanSubscriptionBangumiSourceUrl,
|
||||||
extractMikanSubscriptionSubscriberSourceUrl,
|
extractMikanSubscriptionSubscriberSourceUrl,
|
||||||
} from './mikan';
|
} from './mikan';
|
||||||
@ -144,14 +146,14 @@ export const SYNC_SUBSCRIPTION_SOURCES = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SubscriptionTypedMikanSeasonSchema =
|
export const SubscriptionFormTypedMikanSeasonSchema =
|
||||||
MikanSubscriptionSeasonSourceUrlSchema.and(
|
MikanSubscriptionSeasonSourceUrlSchema.and(
|
||||||
type({
|
type({
|
||||||
credentialId: 'number>0',
|
credentialId: 'number>0',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SubscriptionTypedMikanBangumiSchema = type({
|
export const SubscriptionFormTypedMikanBangumiSchema = type({
|
||||||
category: `'${SubscriptionCategoryEnum.MikanBangumi}'`,
|
category: `'${SubscriptionCategoryEnum.MikanBangumi}'`,
|
||||||
sourceUrl: type.string
|
sourceUrl: type.string
|
||||||
.atLeastLength(1)
|
.atLeastLength(1)
|
||||||
@ -160,7 +162,7 @@ export const SubscriptionTypedMikanBangumiSchema = type({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SubscriptionTypedMikanSubscriberSchema = type({
|
export const SubscriptionFormTypedMikanSubscriberSchema = type({
|
||||||
category: `'${SubscriptionCategoryEnum.MikanSubscriber}'`,
|
category: `'${SubscriptionCategoryEnum.MikanSubscriber}'`,
|
||||||
sourceUrl: type.string
|
sourceUrl: type.string
|
||||||
.atLeastLength(1)
|
.atLeastLength(1)
|
||||||
@ -169,13 +171,36 @@ export const SubscriptionTypedMikanSubscriberSchema = type({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SubscriptionFormTypedSchema =
|
||||||
|
SubscriptionFormTypedMikanSeasonSchema.or(
|
||||||
|
SubscriptionFormTypedMikanBangumiSchema
|
||||||
|
).or(SubscriptionFormTypedMikanSubscriberSchema);
|
||||||
|
|
||||||
|
export const SubscriptionFormSchema = type({
|
||||||
|
enabled: 'boolean',
|
||||||
|
displayName: 'string>0',
|
||||||
|
}).and(SubscriptionFormTypedSchema);
|
||||||
|
|
||||||
|
export type SubscriptionForm = typeof SubscriptionFormSchema.infer;
|
||||||
|
|
||||||
|
export const SubscriptionTypedMikanSeasonSchema =
|
||||||
|
MikanSubscriptionSeasonSourceUrlSchema.and(
|
||||||
|
type({
|
||||||
|
credentialId: 'number>0',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SubscriptionTypedMikanBangumiSchema =
|
||||||
|
MikanSubscriptionBangumiSourceUrlSchema;
|
||||||
|
|
||||||
|
export const SubscriptionTypedMikanSubscriberSchema =
|
||||||
|
MikanSubscriptionSubscriberSourceUrlSchema;
|
||||||
|
|
||||||
export const SubscriptionTypedSchema = SubscriptionTypedMikanSeasonSchema.or(
|
export const SubscriptionTypedSchema = SubscriptionTypedMikanSeasonSchema.or(
|
||||||
SubscriptionTypedMikanBangumiSchema
|
SubscriptionTypedMikanBangumiSchema
|
||||||
).or(SubscriptionTypedMikanSubscriberSchema);
|
).or(SubscriptionTypedMikanSubscriberSchema);
|
||||||
|
|
||||||
export const SubscriptionInsertFormSchema = type({
|
export const SubscriptionSchema = type({
|
||||||
enabled: 'boolean',
|
subscription_id: 'number>0',
|
||||||
displayName: 'string>0',
|
subscriber_id: 'number>0',
|
||||||
}).and(SubscriptionTypedSchema);
|
}).and(SubscriptionTypedSchema);
|
||||||
|
|
||||||
export type SubscriptionInsertForm = typeof SubscriptionInsertFormSchema.infer;
|
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
|
import {
|
||||||
|
type GetTasksQuery,
|
||||||
|
SubscriberTaskTypeEnum,
|
||||||
|
} from '@/infra/graphql/gql/graphql';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
import { type } from 'arktype';
|
||||||
|
import { SubscriptionSchema } from './subscriptions';
|
||||||
|
|
||||||
export const GET_TASKS = gql`
|
export const GET_TASKS = gql`
|
||||||
query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {
|
query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {
|
||||||
@ -9,6 +15,8 @@ export const GET_TASKS = gql`
|
|||||||
) {
|
) {
|
||||||
nodes {
|
nodes {
|
||||||
id,
|
id,
|
||||||
|
job,
|
||||||
|
taskType,
|
||||||
status,
|
status,
|
||||||
attempts,
|
attempts,
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
@ -26,3 +34,28 @@ export const GET_TASKS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const TaskTypedSyncOneSubscriptionFeedsIncrementalSchema = type({
|
||||||
|
taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsIncremental}'`,
|
||||||
|
}).and(SubscriptionSchema);
|
||||||
|
|
||||||
|
export const TaskTypedSyncOneSubscriptionFeedsFullSchema = type({
|
||||||
|
taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionFeedsFull}'`,
|
||||||
|
}).and(SubscriptionSchema);
|
||||||
|
|
||||||
|
export const TaskTypedSyncOneSubscriptionSourcesSchema = type({
|
||||||
|
taskType: `'${SubscriberTaskTypeEnum.SyncOneSubscriptionSources}'`,
|
||||||
|
}).and(SubscriptionSchema);
|
||||||
|
|
||||||
|
export const TaskTypedSchema = TaskTypedSyncOneSubscriptionFeedsFullSchema.or(
|
||||||
|
TaskTypedSyncOneSubscriptionFeedsIncrementalSchema
|
||||||
|
).or(TaskTypedSyncOneSubscriptionSourcesSchema);
|
||||||
|
|
||||||
|
export type TaskTypedDto = typeof TaskTypedSchema.infer;
|
||||||
|
|
||||||
|
export type TaskDto = Omit<
|
||||||
|
GetTasksQuery['subscriberTasks']['nodes'][number],
|
||||||
|
'job'
|
||||||
|
> & {
|
||||||
|
job: TaskTypedDto;
|
||||||
|
};
|
||||||
|
@ -14,16 +14,14 @@ import {
|
|||||||
extractMikanSubscriptionSeasonSourceUrl,
|
extractMikanSubscriptionSeasonSourceUrl,
|
||||||
extractMikanSubscriptionSubscriberSourceUrl,
|
extractMikanSubscriptionSubscriberSourceUrl,
|
||||||
} from '../schema/mikan';
|
} from '../schema/mikan';
|
||||||
import type { SubscriptionInsertForm } from '../schema/subscriptions';
|
import type { SubscriptionForm } from '../schema/subscriptions';
|
||||||
import { MikanService } from './mikan.service';
|
import { MikanService } from './mikan.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SubscriptionService {
|
export class SubscriptionService {
|
||||||
private mikan = inject(MikanService);
|
private mikan = inject(MikanService);
|
||||||
|
|
||||||
transformInsertFormToInput(
|
transformInsertFormToInput(form: SubscriptionForm): SubscriptionsInsertInput {
|
||||||
form: SubscriptionInsertForm
|
|
||||||
): SubscriptionsInsertInput {
|
|
||||||
if (form.category === SubscriptionCategoryEnum.MikanSeason) {
|
if (form.category === SubscriptionCategoryEnum.MikanSeason) {
|
||||||
return {
|
return {
|
||||||
...omit(form, ['seasonStr', 'year']),
|
...omit(form, ['seasonStr', 'year']),
|
||||||
|
@ -18,7 +18,7 @@ export function buildOidcConfig(): OpenIdConfiguration {
|
|||||||
responseType: 'code',
|
responseType: 'code',
|
||||||
silentRenew: true,
|
silentRenew: true,
|
||||||
useRefreshToken: true,
|
useRefreshToken: true,
|
||||||
logLevel: LogLevel.None,
|
logLevel: LogLevel.Warn,
|
||||||
autoUserInfo: !resource,
|
autoUserInfo: !resource,
|
||||||
renewUserInfoAfterTokenRenew: !resource,
|
renewUserInfoAfterTokenRenew: !resource,
|
||||||
customParamsAuthRequest: {
|
customParamsAuthRequest: {
|
||||||
|
@ -15,8 +15,20 @@ export class OidcAuthProvider extends AuthProvider {
|
|||||||
checkAuthResultEvent$ = inject(CHECK_AUTH_RESULT_EVENT);
|
checkAuthResultEvent$ = inject(CHECK_AUTH_RESULT_EVENT);
|
||||||
injector = injectInjector();
|
injector = injectInjector();
|
||||||
|
|
||||||
|
private setupSilentRenew() {
|
||||||
|
const parent = document.defaultView?.parent;
|
||||||
|
if (parent) {
|
||||||
|
const event = new CustomEvent('oidc-silent-renew-message', {
|
||||||
|
detail: document.defaultView?.location!,
|
||||||
|
});
|
||||||
|
parent.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.oidcSecurityService.checkAuth().subscribe();
|
this.oidcSecurityService.checkAuth().subscribe(() => {
|
||||||
|
this.setupSilentRenew();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAuthenticated$() {
|
get isAuthenticated$() {
|
||||||
|
@ -28,7 +28,7 @@ type Documents = {
|
|||||||
"\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsIncrementalDocument,
|
"\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 SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionFeedsFullDocument,
|
||||||
"\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": typeof types.SyncSubscriptionSourcesDocument,
|
"\n mutation 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,
|
"\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": typeof types.GetTasksDocument,
|
||||||
};
|
};
|
||||||
const documents: Documents = {
|
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,
|
"\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,
|
||||||
@ -45,7 +45,7 @@ const documents: Documents = {
|
|||||||
"\n mutation SyncSubscriptionFeedsIncremental($id: Int!) {\n subscriptionSyncOneFeedsIncremental(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsIncrementalDocument,
|
"\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 SyncSubscriptionFeedsFull($id: Int!) {\n subscriptionSyncOneFeedsFull(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionFeedsFullDocument,
|
||||||
"\n mutation SyncSubscriptionSources($id: Int!) {\n subscriptionSyncOneSources(filter: { id: $id }) {\n taskId\n }\n }\n": types.SyncSubscriptionSourcesDocument,
|
"\n mutation 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,
|
"\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n": types.GetTasksDocument,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,7 +121,7 @@ export function gql(source: "\n mutation SyncSubscriptionSources($id: Int!) {\n
|
|||||||
/**
|
/**
|
||||||
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* 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: "\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n"): (typeof documents)["\n query GetTasks($filters: SubscriberTasksFilterInput!, $orderBy: SubscriberTasksOrderInput!, $pagination: PaginationInput!) {\n subscriberTasks(\n pagination: $pagination\n filters: $filters\n orderBy: $orderBy\n ) {\n nodes {\n id,\n job,\n taskType,\n status,\n attempts,\n maxAttempts,\n runAt,\n lastError,\n lockAt,\n lockBy,\n doneAt,\n priority\n }\n paginationInfo {\n total\n pages\n }\n }\n }\n"];
|
||||||
|
|
||||||
export function gql(source: string) {
|
export function gql(source: string) {
|
||||||
return (documents as any)[source] ?? {};
|
return (documents as any)[source] ?? {};
|
||||||
|
@ -14,6 +14,8 @@ export type Scalars = {
|
|||||||
Boolean: { input: boolean; output: boolean; }
|
Boolean: { input: boolean; output: boolean; }
|
||||||
Int: { input: number; output: number; }
|
Int: { input: number; output: number; }
|
||||||
Float: { input: number; output: number; }
|
Float: { input: number; output: number; }
|
||||||
|
/** The `JSON` scalar type represents raw JSON values */
|
||||||
|
Json: { input: any; output: any; }
|
||||||
JsonbFilterInput: { input: any; output: any; }
|
JsonbFilterInput: { input: any; output: any; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,6 +25,7 @@ export type Bangumi = {
|
|||||||
displayName: Scalars['String']['output'];
|
displayName: Scalars['String']['output'];
|
||||||
episode: EpisodesConnection;
|
episode: EpisodesConnection;
|
||||||
fansub?: Maybe<Scalars['String']['output']>;
|
fansub?: Maybe<Scalars['String']['output']>;
|
||||||
|
filter?: Maybe<Scalars['Json']['output']>;
|
||||||
homepage?: Maybe<Scalars['String']['output']>;
|
homepage?: Maybe<Scalars['String']['output']>;
|
||||||
id: Scalars['Int']['output'];
|
id: Scalars['Int']['output'];
|
||||||
mikanBangumiId?: Maybe<Scalars['String']['output']>;
|
mikanBangumiId?: Maybe<Scalars['String']['output']>;
|
||||||
@ -66,6 +69,7 @@ export type BangumiBasic = {
|
|||||||
createdAt: Scalars['String']['output'];
|
createdAt: Scalars['String']['output'];
|
||||||
displayName: Scalars['String']['output'];
|
displayName: Scalars['String']['output'];
|
||||||
fansub?: Maybe<Scalars['String']['output']>;
|
fansub?: Maybe<Scalars['String']['output']>;
|
||||||
|
filter?: Maybe<Scalars['Json']['output']>;
|
||||||
homepage?: Maybe<Scalars['String']['output']>;
|
homepage?: Maybe<Scalars['String']['output']>;
|
||||||
id: Scalars['Int']['output'];
|
id: Scalars['Int']['output'];
|
||||||
mikanBangumiId?: Maybe<Scalars['String']['output']>;
|
mikanBangumiId?: Maybe<Scalars['String']['output']>;
|
||||||
@ -118,6 +122,7 @@ export type BangumiInsertInput = {
|
|||||||
createdAt?: InputMaybe<Scalars['String']['input']>;
|
createdAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
displayName: Scalars['String']['input'];
|
displayName: Scalars['String']['input'];
|
||||||
fansub?: InputMaybe<Scalars['String']['input']>;
|
fansub?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
filter?: InputMaybe<Scalars['Json']['input']>;
|
||||||
homepage?: InputMaybe<Scalars['String']['input']>;
|
homepage?: InputMaybe<Scalars['String']['input']>;
|
||||||
id?: InputMaybe<Scalars['Int']['input']>;
|
id?: InputMaybe<Scalars['Int']['input']>;
|
||||||
mikanBangumiId?: InputMaybe<Scalars['String']['input']>;
|
mikanBangumiId?: InputMaybe<Scalars['String']['input']>;
|
||||||
@ -154,6 +159,7 @@ export type BangumiUpdateInput = {
|
|||||||
createdAt?: InputMaybe<Scalars['String']['input']>;
|
createdAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
displayName?: InputMaybe<Scalars['String']['input']>;
|
displayName?: InputMaybe<Scalars['String']['input']>;
|
||||||
fansub?: InputMaybe<Scalars['String']['input']>;
|
fansub?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
filter?: InputMaybe<Scalars['Json']['input']>;
|
||||||
homepage?: InputMaybe<Scalars['String']['input']>;
|
homepage?: InputMaybe<Scalars['String']['input']>;
|
||||||
id?: InputMaybe<Scalars['Int']['input']>;
|
id?: InputMaybe<Scalars['Int']['input']>;
|
||||||
mikanBangumiId?: InputMaybe<Scalars['String']['input']>;
|
mikanBangumiId?: InputMaybe<Scalars['String']['input']>;
|
||||||
@ -1185,11 +1191,19 @@ export type SubscriberIdFilterInput = {
|
|||||||
eq?: InputMaybe<Scalars['Int']['input']>;
|
eq?: InputMaybe<Scalars['Int']['input']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SubscriberTaskTypeEnum = {
|
||||||
|
SyncOneSubscriptionFeedsFull: 'sync_one_subscription_feeds_full',
|
||||||
|
SyncOneSubscriptionFeedsIncremental: 'sync_one_subscription_feeds_incremental',
|
||||||
|
SyncOneSubscriptionSources: 'sync_one_subscription_sources'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type SubscriberTaskTypeEnum = typeof SubscriberTaskTypeEnum[keyof typeof SubscriberTaskTypeEnum];
|
||||||
export type SubscriberTasks = {
|
export type SubscriberTasks = {
|
||||||
__typename?: 'SubscriberTasks';
|
__typename?: 'SubscriberTasks';
|
||||||
attempts: Scalars['Int']['output'];
|
attempts: Scalars['Int']['output'];
|
||||||
doneAt?: Maybe<Scalars['String']['output']>;
|
doneAt?: Maybe<Scalars['String']['output']>;
|
||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
|
job: Scalars['Json']['output'];
|
||||||
lastError?: Maybe<Scalars['String']['output']>;
|
lastError?: Maybe<Scalars['String']['output']>;
|
||||||
lockAt?: Maybe<Scalars['String']['output']>;
|
lockAt?: Maybe<Scalars['String']['output']>;
|
||||||
lockBy?: Maybe<Scalars['String']['output']>;
|
lockBy?: Maybe<Scalars['String']['output']>;
|
||||||
@ -1199,6 +1213,7 @@ export type SubscriberTasks = {
|
|||||||
status: Scalars['String']['output'];
|
status: Scalars['String']['output'];
|
||||||
subscriber?: Maybe<Subscribers>;
|
subscriber?: Maybe<Subscribers>;
|
||||||
subscriberId: Scalars['Int']['output'];
|
subscriberId: Scalars['Int']['output'];
|
||||||
|
taskType: SubscriberTaskTypeEnum;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscriberTasksBasic = {
|
export type SubscriberTasksBasic = {
|
||||||
@ -1206,6 +1221,7 @@ export type SubscriberTasksBasic = {
|
|||||||
attempts: Scalars['Int']['output'];
|
attempts: Scalars['Int']['output'];
|
||||||
doneAt?: Maybe<Scalars['String']['output']>;
|
doneAt?: Maybe<Scalars['String']['output']>;
|
||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
|
job: Scalars['Json']['output'];
|
||||||
lastError?: Maybe<Scalars['String']['output']>;
|
lastError?: Maybe<Scalars['String']['output']>;
|
||||||
lockAt?: Maybe<Scalars['String']['output']>;
|
lockAt?: Maybe<Scalars['String']['output']>;
|
||||||
lockBy?: Maybe<Scalars['String']['output']>;
|
lockBy?: Maybe<Scalars['String']['output']>;
|
||||||
@ -1214,6 +1230,7 @@ export type SubscriberTasksBasic = {
|
|||||||
runAt: Scalars['String']['output'];
|
runAt: Scalars['String']['output'];
|
||||||
status: Scalars['String']['output'];
|
status: Scalars['String']['output'];
|
||||||
subscriberId: Scalars['Int']['output'];
|
subscriberId: Scalars['Int']['output'];
|
||||||
|
taskType: SubscriberTaskTypeEnum;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscriberTasksConnection = {
|
export type SubscriberTasksConnection = {
|
||||||
@ -1245,12 +1262,14 @@ export type SubscriberTasksFilterInput = {
|
|||||||
runAt?: InputMaybe<TextFilterInput>;
|
runAt?: InputMaybe<TextFilterInput>;
|
||||||
status?: InputMaybe<StringFilterInput>;
|
status?: InputMaybe<StringFilterInput>;
|
||||||
subscriberId?: InputMaybe<SubscriberIdFilterInput>;
|
subscriberId?: InputMaybe<SubscriberIdFilterInput>;
|
||||||
|
taskType?: InputMaybe<StringFilterInput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscriberTasksInsertInput = {
|
export type SubscriberTasksInsertInput = {
|
||||||
attempts: Scalars['Int']['input'];
|
attempts: Scalars['Int']['input'];
|
||||||
doneAt?: InputMaybe<Scalars['String']['input']>;
|
doneAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
id?: InputMaybe<Scalars['String']['input']>;
|
id?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
job: Scalars['Json']['input'];
|
||||||
lastError?: InputMaybe<Scalars['String']['input']>;
|
lastError?: InputMaybe<Scalars['String']['input']>;
|
||||||
lockAt?: InputMaybe<Scalars['String']['input']>;
|
lockAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
lockBy?: InputMaybe<Scalars['String']['input']>;
|
lockBy?: InputMaybe<Scalars['String']['input']>;
|
||||||
@ -1258,6 +1277,7 @@ export type SubscriberTasksInsertInput = {
|
|||||||
priority: Scalars['Int']['input'];
|
priority: Scalars['Int']['input'];
|
||||||
runAt: Scalars['String']['input'];
|
runAt: Scalars['String']['input'];
|
||||||
status: Scalars['String']['input'];
|
status: Scalars['String']['input'];
|
||||||
|
taskType: SubscriberTaskTypeEnum;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscriberTasksOrderInput = {
|
export type SubscriberTasksOrderInput = {
|
||||||
@ -1273,12 +1293,14 @@ export type SubscriberTasksOrderInput = {
|
|||||||
runAt?: InputMaybe<OrderByEnum>;
|
runAt?: InputMaybe<OrderByEnum>;
|
||||||
status?: InputMaybe<OrderByEnum>;
|
status?: InputMaybe<OrderByEnum>;
|
||||||
subscriberId?: InputMaybe<OrderByEnum>;
|
subscriberId?: InputMaybe<OrderByEnum>;
|
||||||
|
taskType?: InputMaybe<OrderByEnum>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SubscriberTasksUpdateInput = {
|
export type SubscriberTasksUpdateInput = {
|
||||||
attempts?: InputMaybe<Scalars['Int']['input']>;
|
attempts?: InputMaybe<Scalars['Int']['input']>;
|
||||||
doneAt?: InputMaybe<Scalars['String']['input']>;
|
doneAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
id?: InputMaybe<Scalars['String']['input']>;
|
id?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
job?: InputMaybe<Scalars['Json']['input']>;
|
||||||
lastError?: InputMaybe<Scalars['String']['input']>;
|
lastError?: InputMaybe<Scalars['String']['input']>;
|
||||||
lockAt?: InputMaybe<Scalars['String']['input']>;
|
lockAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
lockBy?: InputMaybe<Scalars['String']['input']>;
|
lockBy?: InputMaybe<Scalars['String']['input']>;
|
||||||
@ -1286,11 +1308,13 @@ export type SubscriberTasksUpdateInput = {
|
|||||||
priority?: InputMaybe<Scalars['Int']['input']>;
|
priority?: InputMaybe<Scalars['Int']['input']>;
|
||||||
runAt?: InputMaybe<Scalars['String']['input']>;
|
runAt?: InputMaybe<Scalars['String']['input']>;
|
||||||
status?: InputMaybe<Scalars['String']['input']>;
|
status?: InputMaybe<Scalars['String']['input']>;
|
||||||
|
taskType?: InputMaybe<SubscriberTaskTypeEnum>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Subscribers = {
|
export type Subscribers = {
|
||||||
__typename?: 'Subscribers';
|
__typename?: 'Subscribers';
|
||||||
bangumi: BangumiConnection;
|
bangumi: BangumiConnection;
|
||||||
|
bangumiConf?: Maybe<Scalars['Json']['output']>;
|
||||||
createdAt: Scalars['String']['output'];
|
createdAt: Scalars['String']['output'];
|
||||||
credential3rd: Credential3rdConnection;
|
credential3rd: Credential3rdConnection;
|
||||||
displayName: Scalars['String']['output'];
|
displayName: Scalars['String']['output'];
|
||||||
@ -1766,7 +1790,7 @@ export type GetTasksQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
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 type GetTasksQuery = { __typename?: 'Query', subscriberTasks: { __typename?: 'SubscriberTasksConnection', nodes: Array<{ __typename?: 'SubscriberTasks', id: string, job: any, taskType: SubscriberTaskTypeEnum, 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 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>;
|
||||||
@ -1783,4 +1807,4 @@ export const GetSubscriptionDetailDocument = {"kind":"Document","definitions":[{
|
|||||||
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 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 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>;
|
export const GetTasksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTasks"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksFilterInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubscriberTasksOrderInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PaginationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriberTasks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pagination"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pagination"}}},{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"job"}},{"kind":"Field","name":{"kind":"Name","value":"taskType"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"attempts"}},{"kind":"Field","name":{"kind":"Name","value":"maxAttempts"}},{"kind":"Field","name":{"kind":"Name","value":"runAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastError"}},{"kind":"Field","name":{"kind":"Name","value":"lockAt"}},{"kind":"Field","name":{"kind":"Name","value":"lockBy"}},{"kind":"Field","name":{"kind":"Name","value":"doneAt"}},{"kind":"Field","name":{"kind":"Name","value":"priority"}}]}},{"kind":"Field","name":{"kind":"Name","value":"paginationInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"pages"}}]}}]}}]}}]} as unknown as DocumentNode<GetTasksQuery, GetTasksQueryVariables>;
|
@ -1,8 +1,8 @@
|
|||||||
|
import { AUTH_PROVIDER } from '@/infra/auth/auth.provider';
|
||||||
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
|
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
|
||||||
import { setContext } from '@apollo/client/link/context';
|
import { setContext } from '@apollo/client/link/context';
|
||||||
import { Injectable, inject } from '@outposts/injection-js';
|
import { Injectable, inject } from '@outposts/injection-js';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { AUTH_PROVIDER } from '../auth/auth.provider.ts';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphQLService {
|
export class GraphQLService {
|
||||||
@ -33,6 +33,7 @@ export class GraphQLService {
|
|||||||
errorPolicy: 'all',
|
errorPolicy: 'all',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
connectToDevTools: process.env.NODE_ENV === 'development',
|
||||||
});
|
});
|
||||||
|
|
||||||
query = this._apollo.query;
|
query = this._apollo.query;
|
||||||
|
@ -21,8 +21,8 @@ import { useAppForm } from '@/components/ui/tanstack-form';
|
|||||||
import { MikanSeasonEnum } from '@/domains/recorder/schema/mikan';
|
import { MikanSeasonEnum } from '@/domains/recorder/schema/mikan';
|
||||||
import {
|
import {
|
||||||
INSERT_SUBSCRIPTION,
|
INSERT_SUBSCRIPTION,
|
||||||
type SubscriptionInsertForm,
|
type SubscriptionForm,
|
||||||
SubscriptionInsertFormSchema,
|
SubscriptionFormSchema,
|
||||||
} from '@/domains/recorder/schema/subscriptions';
|
} from '@/domains/recorder/schema/subscriptions';
|
||||||
import { SubscriptionService } from '@/domains/recorder/services/subscription.service';
|
import { SubscriptionService } from '@/domains/recorder/services/subscription.service';
|
||||||
import { useInject } from '@/infra/di/inject';
|
import { useInject } from '@/infra/di/inject';
|
||||||
@ -78,11 +78,11 @@ function SubscriptionCreateRouteComponent() {
|
|||||||
credentialId: '',
|
credentialId: '',
|
||||||
year: undefined,
|
year: undefined,
|
||||||
seasonStr: '',
|
seasonStr: '',
|
||||||
} as unknown as SubscriptionInsertForm,
|
} as unknown as SubscriptionForm,
|
||||||
validators: {
|
validators: {
|
||||||
onChangeAsync: SubscriptionInsertFormSchema,
|
onChangeAsync: SubscriptionFormSchema,
|
||||||
onChangeAsyncDebounceMs: 300,
|
onChangeAsyncDebounceMs: 300,
|
||||||
onSubmit: SubscriptionInsertFormSchema,
|
onSubmit: SubscriptionFormSchema,
|
||||||
},
|
},
|
||||||
onSubmit: async (form) => {
|
onSubmit: async (form) => {
|
||||||
const input = subscriptionService.transformInsertFormToInput(form.value);
|
const input = subscriptionService.transformInsertFormToInput(form.value);
|
||||||
@ -152,9 +152,7 @@ function SubscriptionCreateRouteComponent() {
|
|||||||
<Select
|
<Select
|
||||||
value={field.state.value}
|
value={field.state.value}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
field.handleChange(
|
field.handleChange(value as SubscriptionForm['category'])
|
||||||
value as SubscriptionInsertForm['category']
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
|
@ -25,8 +25,8 @@ import { useAppForm } from '@/components/ui/tanstack-form';
|
|||||||
import { MikanSeasonEnum } from '@/domains/recorder/schema/mikan';
|
import { MikanSeasonEnum } from '@/domains/recorder/schema/mikan';
|
||||||
import {
|
import {
|
||||||
GET_SUBSCRIPTION_DETAIL,
|
GET_SUBSCRIPTION_DETAIL,
|
||||||
type SubscriptionInsertForm,
|
type SubscriptionForm,
|
||||||
SubscriptionInsertFormSchema,
|
SubscriptionFormSchema,
|
||||||
UPDATE_SUBSCRIPTIONS,
|
UPDATE_SUBSCRIPTIONS,
|
||||||
} from '@/domains/recorder/schema/subscriptions';
|
} from '@/domains/recorder/schema/subscriptions';
|
||||||
import { SubscriptionService } from '@/domains/recorder/services/subscription.service';
|
import { SubscriptionService } from '@/domains/recorder/services/subscription.service';
|
||||||
@ -125,11 +125,11 @@ function FormView({
|
|||||||
}, [subscription, sourceUrlMeta]);
|
}, [subscription, sourceUrlMeta]);
|
||||||
|
|
||||||
const form = useAppForm({
|
const form = useAppForm({
|
||||||
defaultValues: defaultValues as unknown as SubscriptionInsertForm,
|
defaultValues: defaultValues as unknown as SubscriptionForm,
|
||||||
validators: {
|
validators: {
|
||||||
onChangeAsync: SubscriptionInsertFormSchema,
|
onChangeAsync: SubscriptionFormSchema,
|
||||||
onChangeAsyncDebounceMs: 300,
|
onChangeAsyncDebounceMs: 300,
|
||||||
onSubmit: SubscriptionInsertFormSchema,
|
onSubmit: SubscriptionFormSchema,
|
||||||
},
|
},
|
||||||
onSubmit: async (form) => {
|
onSubmit: async (form) => {
|
||||||
const input = subscriptionService.transformInsertFormToInput(form.value);
|
const input = subscriptionService.transformInsertFormToInput(form.value);
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
export const TaskActionsView = memo(() => {
|
||||||
|
return null;
|
||||||
|
});
|
@ -1,5 +1,34 @@
|
|||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { DataTablePagination } from '@/components/ui/data-table-pagination';
|
||||||
|
import { DataTableRowActions } from '@/components/ui/data-table-row-actions';
|
||||||
|
import { DetailEmptyView } from '@/components/ui/detail-empty-view';
|
||||||
|
import { QueryErrorView } from '@/components/ui/query-error-view';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { GET_TASKS, type TaskDto } from '@/domains/recorder/schema/tasks';
|
||||||
|
import type { GetTasksQuery } from '@/infra/graphql/gql/graphql';
|
||||||
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { useDebouncedSkeleton } from '@/presentation/hooks/use-debounded-skeleton';
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
|
import {
|
||||||
|
type ColumnDef,
|
||||||
|
type PaginationState,
|
||||||
|
type SortingState,
|
||||||
|
type VisibilityState,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
CheckCircle,
|
||||||
|
Clock,
|
||||||
|
Loader2,
|
||||||
|
RefreshCw,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
export const Route = createFileRoute('/_app/tasks/manage')({
|
export const Route = createFileRoute('/_app/tasks/manage')({
|
||||||
component: TaskManageRouteComponent,
|
component: TaskManageRouteComponent,
|
||||||
@ -9,5 +38,340 @@ export const Route = createFileRoute('/_app/tasks/manage')({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function TaskManageRouteComponent() {
|
function TaskManageRouteComponent() {
|
||||||
return <div>Hello "/_app/tasks/manage"!</div>;
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
|
||||||
|
lockAt: false,
|
||||||
|
lockBy: false,
|
||||||
|
attempts: false,
|
||||||
|
});
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [pagination, setPagination] = useState<PaginationState>({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { loading, error, data, refetch } = useQuery<GetTasksQuery>(GET_TASKS, {
|
||||||
|
variables: {
|
||||||
|
pagination: {
|
||||||
|
page: {
|
||||||
|
page: pagination.pageIndex,
|
||||||
|
limit: pagination.pageSize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filters: {},
|
||||||
|
orderBy: {
|
||||||
|
runAt: 'DESC',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pollInterval: 5000, // Auto-refresh every 5 seconds
|
||||||
|
});
|
||||||
|
|
||||||
|
const { showSkeleton } = useDebouncedSkeleton({ loading });
|
||||||
|
|
||||||
|
const tasks = data?.subscriberTasks;
|
||||||
|
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
const cs: ColumnDef<TaskDto>[] = [
|
||||||
|
{
|
||||||
|
header: 'ID',
|
||||||
|
accessorKey: 'id',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="max-w-[200px] truncate font-mono text-sm"
|
||||||
|
title={row.original.id}
|
||||||
|
>
|
||||||
|
{row.original.id}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Status',
|
||||||
|
accessorKey: 'status',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return getStatusBadge(row.original.status);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Priority',
|
||||||
|
accessorKey: 'priority',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return getPriorityBadge(row.original.priority);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Attempts',
|
||||||
|
accessorKey: 'attempts',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const attempts = row.original.attempts;
|
||||||
|
const maxAttempts = row.original.maxAttempts;
|
||||||
|
return (
|
||||||
|
<div className="text-sm">
|
||||||
|
{attempts} / {maxAttempts}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Run At',
|
||||||
|
accessorKey: 'runAt',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const runAt = row.original.runAt;
|
||||||
|
return (
|
||||||
|
<div className="text-sm">
|
||||||
|
{format(new Date(runAt), 'yyyy-MM-dd HH:mm:ss')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Done At',
|
||||||
|
accessorKey: 'doneAt',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const doneAt = row.original.doneAt;
|
||||||
|
return (
|
||||||
|
<div className="text-sm">
|
||||||
|
{doneAt ? format(new Date(doneAt), 'yyyy-MM-dd HH:mm:ss') : '-'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Last Error',
|
||||||
|
accessorKey: 'lastError',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const lastError = row.original.lastError;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="max-w-xs truncate text-sm"
|
||||||
|
title={lastError || undefined}
|
||||||
|
>
|
||||||
|
{lastError || '-'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Lock At',
|
||||||
|
accessorKey: 'lockAt',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const lockAt = row.original.lockAt;
|
||||||
|
return (
|
||||||
|
<div className="text-sm">
|
||||||
|
{lockAt ? format(new Date(lockAt), 'yyyy-MM-dd HH:mm:ss') : '-'}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Lock By',
|
||||||
|
accessorKey: 'lockBy',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const lockBy = row.original.lockBy;
|
||||||
|
return <div className="font-mono text-sm">{lockBy || '-'}</div>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<DataTableRowActions
|
||||||
|
row={row}
|
||||||
|
getId={(row) => row.original.id}
|
||||||
|
showDetail
|
||||||
|
onDetail={() => {
|
||||||
|
navigate({
|
||||||
|
to: '/tasks/detail/$id',
|
||||||
|
params: { id: row.original.id },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return cs;
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: useMemo(() => (tasks?.nodes ?? []) as TaskDto[], [tasks]),
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
pageCount: tasks?.paginationInfo?.pages,
|
||||||
|
rowCount: tasks?.paginationInfo?.total,
|
||||||
|
enableColumnPinning: true,
|
||||||
|
autoResetPageIndex: true,
|
||||||
|
manualPagination: true,
|
||||||
|
state: {
|
||||||
|
pagination,
|
||||||
|
sorting,
|
||||||
|
columnVisibility,
|
||||||
|
},
|
||||||
|
initialState: {
|
||||||
|
columnPinning: {
|
||||||
|
right: ['actions'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <QueryErrorView message={error.message} onRetry={refetch} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto space-y-4 px-4">
|
||||||
|
<div className="flex items-center justify-between pt-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="font-bold text-2xl">Subscription Management</h1>
|
||||||
|
<p className="text-muted-foreground">Manage your subscription</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={() => refetch()} variant="outline" size="sm">
|
||||||
|
<RefreshCw className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{showSkeleton &&
|
||||||
|
Array.from(new Array(10)).map((_, index) => (
|
||||||
|
<Skeleton key={index} className="h-32 w-full" />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{!showSkeleton && table.getRowModel().rows?.length > 0 ? (
|
||||||
|
table.getRowModel().rows.map((row) => {
|
||||||
|
const task = row.original;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={task.id}
|
||||||
|
className="space-y-3 rounded-lg border bg-card p-4"
|
||||||
|
>
|
||||||
|
{/* Header with status and priority */}
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<div className="font-mono text-muted-foreground text-xs">
|
||||||
|
# {task.id}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Badge variant="outline">{task.taskType}</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 flex items-center gap-2">
|
||||||
|
{getStatusBadge(task.status)}
|
||||||
|
<div className="mr-0 ml-auto">
|
||||||
|
<DataTableRowActions
|
||||||
|
row={row}
|
||||||
|
getId={(r) => r.original.id}
|
||||||
|
showDetail
|
||||||
|
onDetail={() => {
|
||||||
|
navigate({
|
||||||
|
to: '/tasks/detail/$id',
|
||||||
|
params: { id: task.id },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{task.job && (
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="text-muted-foreground">Job: </span>
|
||||||
|
<br />
|
||||||
|
<span
|
||||||
|
className="whitespace-pre-wrap"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(task.job, null, 2),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Time info */}
|
||||||
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Run at: </span>
|
||||||
|
<span>{format(new Date(task.runAt), 'MM/dd HH:mm')}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Done: </span>
|
||||||
|
<span>
|
||||||
|
{task.doneAt
|
||||||
|
? format(new Date(task.doneAt), 'MM/dd HH:mm')
|
||||||
|
: '-'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Attempts */}
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="text-muted-foreground">Attempts: </span>
|
||||||
|
<span>
|
||||||
|
{task.attempts} / {task.maxAttempts}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Priority */}
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="text-muted-foreground">Priority: </span>
|
||||||
|
<span>{task.priority}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Error if exists */}
|
||||||
|
{task.status === 'error' && task.lastError && (
|
||||||
|
<div className="rounded bg-destructive/10 p-2 text-destructive text-sm">
|
||||||
|
{task.lastError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<DetailEmptyView message="No tasks found" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTablePagination table={table} showSelectedRowCount={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusBadge(status: string) {
|
||||||
|
switch (status.toLowerCase()) {
|
||||||
|
case 'completed':
|
||||||
|
case 'done':
|
||||||
|
return (
|
||||||
|
<Badge variant="secondary" className="bg-green-100 text-green-800">
|
||||||
|
<CheckCircle className="mr-1 h-3 w-3" />
|
||||||
|
Completed
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
case 'running':
|
||||||
|
case 'active':
|
||||||
|
return (
|
||||||
|
<Badge variant="secondary" className="bg-blue-100 text-blue-800">
|
||||||
|
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
||||||
|
Running
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
case 'failed':
|
||||||
|
case 'error':
|
||||||
|
return (
|
||||||
|
<Badge variant="destructive">
|
||||||
|
<AlertCircle className="mr-1 h-3 w-3" />
|
||||||
|
Failed
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
case 'pending':
|
||||||
|
case 'waiting':
|
||||||
|
return (
|
||||||
|
<Badge variant="secondary" className="bg-yellow-100 text-yellow-800">
|
||||||
|
<Clock className="mr-1 h-3 w-3" />
|
||||||
|
Pending
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return <Badge variant="outline">{status}</Badge>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user