feat: add tasks manage view

This commit is contained in:
2025-06-12 03:32:18 +08:00
parent 258eeddc74
commit 07955286f1
32 changed files with 774 additions and 348 deletions

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use async_graphql::dynamic::ValueAccessor;
use async_graphql::dynamic::{ResolverContext, ValueAccessor};
use sea_orm::{EntityTrait, Value as SeaValue};
use seaography::{BuilderContext, SeaResult};
@@ -25,11 +25,15 @@ fn register_crypto_column_input_conversion_to_schema_context<T>(
context.types.input_conversions.insert(
format!("{entity_name}.{column_name}"),
Box::new(move |value: &ValueAccessor| -> SeaResult<sea_orm::Value> {
let source = value.string()?;
let encrypted = ctx.crypto().encrypt_string(source.into())?;
Ok(encrypted.into())
}),
Box::new(
move |_resolve_context: &ResolverContext<'_>,
value: &ValueAccessor|
-> SeaResult<sea_orm::Value> {
let source = value.string()?;
let encrypted = ctx.crypto().encrypt_string(source.into())?;
Ok(encrypted.into())
},
),
);
}

View File

@@ -1,23 +1,10 @@
use async_graphql::dynamic::Scalar;
use seaography::{Builder as SeaographyBuilder, BuilderContext, ConvertedType};
use seaography::{Builder as SeaographyBuilder, BuilderContext};
use crate::{
graphql::infra::{
json::restrict_jsonb_filter_input_for_entity,
util::{get_column_key, get_entity_key},
},
models::subscriber_tasks::{self, SubscriberTask},
graphql::infra::json::restrict_jsonb_filter_input_for_entity, models::subscriber_tasks,
};
pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext) {
let entity_key = get_entity_key::<subscriber_tasks::Entity>(context);
let column_name =
get_column_key::<subscriber_tasks::Entity>(context, &subscriber_tasks::Column::Job);
let column_name = context.entity_object.column_name.as_ref()(&entity_key, &column_name);
context.types.overwrites.insert(
column_name,
ConvertedType::Custom(String::from("SubscriberTask")),
);
restrict_jsonb_filter_input_for_entity::<subscriber_tasks::Entity>(
context,
&subscriber_tasks::Column::Job,
@@ -27,16 +14,6 @@ pub fn register_subscriber_tasks_to_schema_context(context: &mut BuilderContext)
pub fn register_subscriber_tasks_to_schema_builder(
mut builder: SeaographyBuilder,
) -> SeaographyBuilder {
let subscriber_tasks_scalar = Scalar::new("SubscriberTasks")
.description("The subscriber tasks")
.validator(|value| -> bool {
if let Ok(json) = value.clone().into_json() {
serde_json::from_value::<SubscriberTask>(json).is_ok()
} else {
false
}
});
builder.schema = builder.schema.register(subscriber_tasks_scalar);
builder.register_enumeration::<subscriber_tasks::SubscriberTaskType>();
builder
}

View File

@@ -1,14 +1,29 @@
use std::sync::Arc;
use async_graphql::dynamic::{ResolverContext, ValueAccessor};
use sea_orm::EntityTrait;
use seaography::{BuilderContext, FnGuard, GuardAction};
use async_graphql::dynamic::{ObjectAccessor, ResolverContext, TypeRef, ValueAccessor};
use lazy_static::lazy_static;
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::{
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(
value: ValueAccessor<'_>,
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
}

View File

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

View File

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

View File

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