From 0d28b0b968468f821e9b6cf7d53e6d95af22e710 Mon Sep 17 00:00:00 2001 From: doug-martin Date: Fri, 17 Jul 2020 01:43:49 -0500 Subject: [PATCH] fix(graphql): Fix filters to transform to expected type #317 --- .../src/resolvers/create.resolver.ts | 13 ++------- .../src/resolvers/delete.resolver.ts | 13 ++------- .../query-graphql/src/resolvers/helpers.ts | 20 ++++++++++++- .../src/resolvers/update.resolver.ts | 14 ++------- .../field-comparison.factory.ts | 29 ++++++++++++++----- 5 files changed, 49 insertions(+), 40 deletions(-) diff --git a/packages/query-graphql/src/resolvers/create.resolver.ts b/packages/query-graphql/src/resolvers/create.resolver.ts index df910a157..4bfcc2f6e 100644 --- a/packages/query-graphql/src/resolvers/create.resolver.ts +++ b/packages/query-graphql/src/resolvers/create.resolver.ts @@ -3,7 +3,7 @@ * @packageDocumentation */ // eslint-disable-next-line max-classes-per-file -import { Class, DeepPartial, applyFilter } from '@nestjs-query/core'; +import { Class, DeepPartial } from '@nestjs-query/core'; import { Args, ArgsType, InputType, PartialType, Resolver } from '@nestjs/graphql'; import omit from 'lodash.omit'; import { DTONames, getDTONames } from '../common'; @@ -16,7 +16,7 @@ import { SubscriptionArgsType, SubscriptionFilterInputType, } from '../types'; -import { transformAndValidate } from './helpers'; +import { createSubscriptionFilter, transformAndValidate } from './helpers'; import { BaseServiceResolver, ResolverClass, ServiceResolver, SubscriptionResolverOpts } from './resolver.interface'; export type CreatedEvent = { [eventName: string]: DTO }; @@ -115,14 +115,7 @@ export const Creatable = >(DTOClass: Class, class SA extends SubscriptionArgsType(SI) {} // eslint-disable-next-line @typescript-eslint/no-explicit-any - const subscriptionFilter = (payload: any, variables: SA): boolean => { - if (variables.input?.filter) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const dto = payload[createdEvent] as DTO; - return applyFilter(dto, variables.input.filter); - } - return true; - }; + const subscriptionFilter = createSubscriptionFilter(SI, createdEvent); @Resolver(() => DTOClass, { isAbstract: true }) class CreateResolverBase extends BaseClass { diff --git a/packages/query-graphql/src/resolvers/delete.resolver.ts b/packages/query-graphql/src/resolvers/delete.resolver.ts index c6bbc87bc..499e33227 100644 --- a/packages/query-graphql/src/resolvers/delete.resolver.ts +++ b/packages/query-graphql/src/resolvers/delete.resolver.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line max-classes-per-file -import { applyFilter, Class, DeleteManyResponse } from '@nestjs-query/core'; +import { Class, DeleteManyResponse } from '@nestjs-query/core'; import omit from 'lodash.omit'; import { ObjectType, ArgsType, Resolver, Args, PartialType, InputType } from '@nestjs/graphql'; import { DTONames, getDTONames } from '../common'; @@ -14,7 +14,7 @@ import { SubscriptionFilterInputType, } from '../types'; import { ResolverMutation, ResolverSubscription } from '../decorators'; -import { transformAndValidate } from './helpers'; +import { createSubscriptionFilter, transformAndValidate } from './helpers'; export type DeletedEvent = { [eventName: string]: DTO }; export interface DeleteResolverOpts extends SubscriptionResolverOpts { @@ -85,14 +85,7 @@ export const Deletable = (DTOClass: Class, opts: DeleteResolverOpts
{ - if (variables.input?.filter) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const dto = payload[deletedOneEvent] as DTO; - return applyFilter(dto, variables.input.filter); - } - return true; - }; + const deleteOneSubscriptionFilter = createSubscriptionFilter(SI, deletedOneEvent); @Resolver(() => DTOClass, { isAbstract: true }) class DeleteResolverBase extends BaseClass { diff --git a/packages/query-graphql/src/resolvers/helpers.ts b/packages/query-graphql/src/resolvers/helpers.ts index b50107323..29ad91c53 100644 --- a/packages/query-graphql/src/resolvers/helpers.ts +++ b/packages/query-graphql/src/resolvers/helpers.ts @@ -1,7 +1,8 @@ -import { Class } from '@nestjs-query/core'; +import { applyFilter, Class } from '@nestjs-query/core'; import { plainToClass } from 'class-transformer'; import { validate } from 'class-validator'; import { BadRequestException } from '@nestjs/common'; +import { SubscriptionArgsType, SubscriptionFilterInputType } from '../types'; /** @internal */ export const transformAndValidate = async (TClass: Class, partial: T): Promise => { @@ -15,3 +16,20 @@ export const transformAndValidate = async (TClass: Class, partial: T): Pro } return transformed; }; + +export const createSubscriptionFilter = >( + InputClass: Class, + payloadKey: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): ((payload: any, variables: SubscriptionArgsType, context: any) => boolean | Promise) => { + return async (payload: any, variables: SubscriptionArgsType): Promise => { + const { input } = variables; + if (input) { + const args = await transformAndValidate(InputClass, input); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const dto = payload[payloadKey] as DTO; + return applyFilter(dto, args.filter || {}); + } + return true; + }; +}; diff --git a/packages/query-graphql/src/resolvers/update.resolver.ts b/packages/query-graphql/src/resolvers/update.resolver.ts index 8d8cfa6fa..5c7543c5c 100644 --- a/packages/query-graphql/src/resolvers/update.resolver.ts +++ b/packages/query-graphql/src/resolvers/update.resolver.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line max-classes-per-file -import { applyFilter, Class, DeepPartial, DeleteManyResponse, UpdateManyResponse } from '@nestjs-query/core'; +import { Class, DeepPartial, DeleteManyResponse, UpdateManyResponse } from '@nestjs-query/core'; import { ArgsType, InputType, Resolver, Args, PartialType } from '@nestjs/graphql'; import omit from 'lodash.omit'; import { DTONames, getDTONames } from '../common'; @@ -14,7 +14,7 @@ import { } from '../types'; import { BaseServiceResolver, ResolverClass, ServiceResolver, SubscriptionResolverOpts } from './resolver.interface'; import { ResolverMutation, ResolverSubscription } from '../decorators'; -import { transformAndValidate } from './helpers'; +import { createSubscriptionFilter, transformAndValidate } from './helpers'; export type UpdatedEvent = { [eventName: string]: DTO }; export interface UpdateResolverOpts = DeepPartial> @@ -113,15 +113,7 @@ export const Updateable = >(DTOClass: Class @ArgsType() class UOSA extends SubscriptionArgsType(SI) {} - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const updateOneSubscriptionFilter = (payload: any, variables: UOSA): boolean => { - if (variables.input?.filter) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const dto = payload[updateOneEvent] as DTO; - return applyFilter(dto, variables.input.filter); - } - return true; - }; + const updateOneSubscriptionFilter = createSubscriptionFilter(SI, updateOneEvent); @Resolver(() => DTOClass, { isAbstract: true }) class UpdateResolverBase extends BaseClass { diff --git a/packages/query-graphql/src/types/query/field-comparison/field-comparison.factory.ts b/packages/query-graphql/src/types/query/field-comparison/field-comparison.factory.ts index a58db1149..9e21f3bbd 100644 --- a/packages/query-graphql/src/types/query/field-comparison/field-comparison.factory.ts +++ b/packages/query-graphql/src/types/query/field-comparison/field-comparison.factory.ts @@ -12,6 +12,7 @@ import { GraphQLTimestamp, GraphQLISODateTime, } from '@nestjs/graphql'; +import { Type } from 'class-transformer'; import { getMetadataStorage } from '../../../metadata'; import { IsUndefined } from '../../validators'; import { getOrCreateFloatFieldComparison } from './float-field-comparison.type'; @@ -46,24 +47,24 @@ const knownTypes: Set = new Set([ ]); // eslint-disable-next-line @typescript-eslint/no-explicit-any -const isNamed = (Type: any): Type is { name: string } => { +const isNamed = (SomeType: any): SomeType is { name: string } => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return 'name' in Type && typeof Type.name === 'string'; + return 'name' in SomeType && typeof SomeType.name === 'string'; }; /** @internal */ -const getTypeName = (Type: ReturnTypeFuncValue): string => { - if (knownTypes.has(Type) || isNamed(Type)) { - const typeName = (Type as { name: string }).name; +const getTypeName = (SomeType: ReturnTypeFuncValue): string => { + if (knownTypes.has(SomeType) || isNamed(SomeType)) { + const typeName = (SomeType as { name: string }).name; return upperCaseFirst(typeName); } - if (typeof Type === 'object') { - const enumType = getMetadataStorage().getGraphqlEnumMetadata(Type); + if (typeof SomeType === 'object') { + const enumType = getMetadataStorage().getGraphqlEnumMetadata(SomeType); if (enumType) { return upperCaseFirst(enumType.name); } } - throw new Error(`Unable to create filter comparison for ${JSON.stringify(Type)}.`); + throw new Error(`Unable to create filter comparison for ${JSON.stringify(SomeType)}.`); }; /** @internal */ @@ -92,50 +93,62 @@ export function createFilterComparisonType( @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) eq?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) neq?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) gt?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) gte?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) lt?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) lte?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) like?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) notLike?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) iLike?: T; @Field(() => fieldType, { nullable: true }) @IsUndefined() + @Type(() => TClass) notILike?: T; @Field(() => [fieldType], { nullable: true }) @IsUndefined() + @Type(() => TClass) in?: T[]; @Field(() => [fieldType], { nullable: true }) @IsUndefined() + @Type(() => TClass) notIn?: T[]; }