feature: add mutation input object transformer

This commit is contained in:
master 2025-04-28 02:44:16 +08:00
parent ee1b1ae5e6
commit 0300d7baf6
12 changed files with 145 additions and 65 deletions

2
Cargo.lock generated
View File

@ -5632,7 +5632,7 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "seaography"
version = "1.1.4"
source = "git+https://github.com/lonelyhentxi/seaography.git?rev=0c4cc5c#0c4cc5ccd43730338802f98401120d5faeccd096"
source = "git+https://github.com/dumtruck/seaography.git?rev=10ba248#10ba2487fb356a0385c598290668a01e0ef21734"
dependencies = [
"async-graphql",
"fnv",

View File

@ -50,4 +50,4 @@ recorder = { path = "./apps/recorder" }
[patch.crates-io]
jwt-authorizer = { git = "https://github.com/blablacio/jwt-authorizer.git", rev = "e956774" }
seaography = { git = "https://github.com/lonelyhentxi/seaography.git", rev = "0c4cc5c" }
seaography = { git = "https://github.com/dumtruck/seaography.git", rev = "10ba248" }

View File

@ -97,64 +97,75 @@ where
Ok(user_info) => {
let subscriber_id = user_info.subscriber_auth.subscriber_id;
let validation_result = match context.field().name() {
field if field == entity_create_one_mutation_field_name.as_str() => context
.args
.try_get(&entity_create_one_mutation_data_field_name)
.and_then(|data_value| {
field if field == entity_create_one_mutation_field_name.as_str() => {
if let Some(data_value) = context
.args
.get(&entity_create_one_mutation_data_field_name)
{
guard_data_object_accessor_with_subscriber_id(
data_value,
&column_name,
subscriber_id,
)
})
.map_err(|inner_error| {
AuthError::from_graphql_subscribe_id_guard(
inner_error,
context,
&entity_create_one_mutation_data_field_name,
&column_name,
)
}),
field if field == entity_create_batch_mutation_field_name.as_str() => context
.args
.try_get(&entity_create_batch_mutation_data_field_name)
.and_then(|data_value| {
data_value.list().and_then(|data_list| {
data_list.iter().try_for_each(|data_item_value| {
guard_data_object_accessor_with_subscriber_id(
data_item_value,
&column_name,
subscriber_id,
)
})
})
})
.map_err(|inner_error| {
AuthError::from_graphql_subscribe_id_guard(
inner_error,
context,
&entity_create_batch_mutation_data_field_name,
&column_name,
)
}),
field if field == entity_update_mutation_field_name.as_str() => {
match context.args.get(&entity_update_mutation_data_field_name) {
Some(data_value) => {
guard_data_object_accessor_with_optional_subscriber_id(
data_value,
.map_err(|inner_error| {
AuthError::from_graphql_subscribe_id_guard(
inner_error,
context,
&entity_create_one_mutation_data_field_name,
&column_name,
subscriber_id,
)
})
} else {
Ok(())
}
}
field if field == entity_create_batch_mutation_field_name.as_str() => {
if let Some(data_value) = context
.args
.get(&entity_create_batch_mutation_data_field_name)
{
data_value
.list()
.and_then(|data_list| {
data_list.iter().try_for_each(|data_item_value| {
guard_data_object_accessor_with_optional_subscriber_id(
data_item_value,
&column_name,
subscriber_id,
)
})
})
.map_err(|inner_error| {
AuthError::from_graphql_subscribe_id_guard(
inner_error,
context,
&entity_update_mutation_data_field_name,
&entity_create_batch_mutation_data_field_name,
&column_name,
)
})
}
None => Ok(()),
} else {
Ok(())
}
}
field if field == entity_update_mutation_field_name.as_str() => {
if let Some(data_value) =
context.args.get(&entity_update_mutation_data_field_name)
{
guard_data_object_accessor_with_optional_subscriber_id(
data_value,
&column_name,
subscriber_id,
)
.map_err(|inner_error| {
AuthError::from_graphql_subscribe_id_guard(
inner_error,
context,
&entity_update_mutation_data_field_name,
&column_name,
)
})
} else {
Ok(())
}
}
_ => Ok(()),

View File

@ -3,7 +3,7 @@ use once_cell::sync::OnceCell;
use sea_orm::{DatabaseConnection, EntityTrait, Iterable};
use seaography::{Builder, BuilderContext, FilterType, FilterTypesMapHelper};
use super::transformer::filter_condition_transformer;
use super::transformer::{filter_condition_transformer, mutation_input_object_transformer};
use crate::graphql::{
filter::{
SUBSCRIBER_ID_FILTER_INFO, init_custom_filter_info, subscriber_id_condition_function,
@ -48,13 +48,25 @@ where
)),
);
context.filter_types.condition_functions.insert(
entity_column_key,
entity_column_key.clone(),
subscriber_id_condition_function::<T>(context, column),
);
context.transformers.filter_conditions_transformers.insert(
entity_key,
entity_key.clone(),
filter_condition_transformer::<T>(context, column),
);
context
.transformers
.mutation_input_object_transformers
.insert(
entity_key,
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 schema(

View File

@ -1,7 +1,10 @@
use async_graphql::dynamic::ResolverContext;
use sea_orm::{ColumnTrait, Condition, EntityTrait};
use seaography::{BuilderContext, FnFilterConditionsTransformer};
use std::{collections::BTreeMap, sync::Arc};
use async_graphql::dynamic::ResolverContext;
use sea_orm::{ColumnTrait, Condition, EntityTrait, Value};
use seaography::{BuilderContext, FnFilterConditionsTransformer, FnMutationInputObjectTransformer};
use super::util::{get_column_key, get_entity_key};
use crate::auth::AuthUserInfo;
pub fn filter_condition_transformer<T>(
@ -25,3 +28,56 @@ where
},
)
}
pub fn 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, Value>|
-> BTreeMap<String, Value> {
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(),
Value::Int(Some(subscriber_id)),
);
}
input
}
Err(err) => unreachable!("auth user info must be guarded: {:?}", err),
}
} else {
input
}
},
)
}

View File

@ -66,7 +66,7 @@ export function authContextFromInjector(injector: Injector): AuthContext {
return {
type: authProvider.authMethod,
isAuthenticated$: authService.isAuthenticated$,
userData$: authService.userData$,
userData$: authService.authData$,
checkAuthResultEvent$: authService.checkAuthResultEvent$,
authService,
authProvider,

View File

@ -20,14 +20,14 @@ export function useAuth() {
[authContext.isAuthenticated$]
);
const userData = useMemo(
const authData = useMemo(
() => atomWithObservable(() => authContext.userData$ as Observable<any>),
[authContext]
);
return {
...authContext,
userData,
authData,
injector,
isAuthenticated,
};

View File

@ -6,8 +6,8 @@ export class AuthService {
private authProvider = inject(AUTH_PROVIDER);
isAuthenticated$ = this.authProvider.isAuthenticated$;
userData$ = this.authProvider.userData$;
checkAuthResultEvent$ = this.authProvider.checkAuthResultEvent$;
authData$ = this.authProvider.authData$;
setup() {
this.authProvider.setup();

View File

@ -7,7 +7,7 @@ export abstract class AuthProvider {
abstract authMethod: AuthMethodType;
abstract checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
abstract isAuthenticated$: Observable<boolean>;
abstract userData$: Observable<any>;
abstract authData$: Observable<any>;
abstract getAccessToken(): Observable<string | undefined>;
abstract setup(): void;
abstract autoLoginPartialRoutesGuard(): Observable<boolean>;

View File

@ -7,7 +7,7 @@ import { AUTH_METHOD } from '../defs';
export class BasicAuthProvider extends AuthProvider {
authMethod = AUTH_METHOD.BASIC;
isAuthenticated$ = of(true);
userData$ = of({});
authData$ = of({});
checkAuthResultEvent$: Observable<CheckAuthResultEventType> = NEVER;
getAccessToken(): Observable<string | undefined> {

View File

@ -25,7 +25,7 @@ export class OidcAuthProvider extends AuthProvider {
);
}
get userData$() {
get authData$() {
return this.oidcSecurityService.userData$.pipe(map((s) => s.userData));
}

View File

@ -56,20 +56,21 @@ const CREATE_SUBSCRIPTION_MUTATION = gql`
sourceUrl
enabled
category
subscriberId
}
}
`;
function SubscriptionCreateRouteComponent() {
const { userData } = useAuth();
console.log(JSON.stringify(userData, null, 2));
const { authData } = useAuth();
console.log(JSON.stringify(authData, null, 2));
const [isSubmitting, setIsSubmitting] = useState(false);
const navigate = useNavigate();
const form = useForm<SubscriptionFormValues>({
defaultValues: {
displayName: '',
sourceUrl: '',
category: 'Mikan',
category: 'mikan',
enabled: true,
},
});
@ -129,7 +130,7 @@ function SubscriptionCreateRouteComponent() {
disabled
value={field.value}
onValueChange={field.onChange}
defaultValue="Mikan"
defaultValue="mikan"
>
<FormControl>
<SelectTrigger>
@ -137,11 +138,11 @@ function SubscriptionCreateRouteComponent() {
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="Mikan">Mikan</SelectItem>
<SelectItem value="mikan">mikan</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Currently only Mikan source is supported
Currently only mikan source is supported
</FormDescription>
<FormMessage />
</FormItem>