diff --git a/server/src/workspace/utils/get-resolver-name.util.ts b/server/src/workspace/utils/get-resolver-name.util.ts index f8f5a7a81..d1a60e790 100644 --- a/server/src/workspace/utils/get-resolver-name.util.ts +++ b/server/src/workspace/utils/get-resolver-name.util.ts @@ -21,6 +21,10 @@ export const getResolverName = ( return `update${pascalCase(objectMetadata.nameSingular)}`; case 'deleteOne': return `delete${pascalCase(objectMetadata.nameSingular)}`; + case 'updateMany': + return `update${pascalCase(objectMetadata.namePlural)}`; + case 'deleteMany': + return `delete${pascalCase(objectMetadata.namePlural)}`; default: throw new Error(`Unknown resolver type: ${type}`); } diff --git a/server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts b/server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts new file mode 100644 index 000000000..276b73e5f --- /dev/null +++ b/server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface'; +import { DeleteManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; + +import { FieldsStringFactory } from './fields-string.factory'; + +@Injectable() +export class DeleteManyQueryFactory { + constructor(private readonly fieldsStringFactory: FieldsStringFactory) {} + + create(args: DeleteManyResolverArgs, options: WorkspaceQueryBuilderOptions) { + const fieldsString = this.fieldsStringFactory.create( + options.info, + options.fieldMetadataCollection, + ); + + return ` + mutation { + deleteFrom${ + options.targetTableName + }Collection(filter: ${stringifyWithoutKeyQuote(args.filter)}) { + affectedCount + records { + ${fieldsString} + } + } + } + `; + } +} diff --git a/server/src/workspace/workspace-query-builder/factories/factories.ts b/server/src/workspace/workspace-query-builder/factories/factories.ts index b1cae30df..7790d2d97 100644 --- a/server/src/workspace/workspace-query-builder/factories/factories.ts +++ b/server/src/workspace/workspace-query-builder/factories/factories.ts @@ -8,6 +8,8 @@ import { FieldsStringFactory } from './fields-string.factory'; import { FindManyQueryFactory } from './find-many-query.factory'; import { FindOneQueryFactory } from './find-one-query.factory'; import { UpdateOneQueryFactory } from './update-one-query.factory'; +import { UpdateManyQueryFactory } from './update-many-query.factory'; +import { DeleteManyQueryFactory } from './delete-many-query.factory'; export const workspaceQueryBuilderFactories = [ ArgsAliasFactory, @@ -20,4 +22,6 @@ export const workspaceQueryBuilderFactories = [ FindManyQueryFactory, FindOneQueryFactory, UpdateOneQueryFactory, + UpdateManyQueryFactory, + DeleteManyQueryFactory, ]; diff --git a/server/src/workspace/workspace-query-builder/factories/update-many-query.factory.ts b/server/src/workspace/workspace-query-builder/factories/update-many-query.factory.ts new file mode 100644 index 000000000..46d963607 --- /dev/null +++ b/server/src/workspace/workspace-query-builder/factories/update-many-query.factory.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; + +import { + Record as IRecord, + RecordFilter, +} from 'src/workspace/workspace-query-builder/interfaces/record.interface'; +import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface'; +import { UpdateManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; +import { FieldsStringFactory } from 'src/workspace/workspace-query-builder/factories/fields-string.factory'; +import { ArgsAliasFactory } from 'src/workspace/workspace-query-builder/factories/args-alias.factory'; + +@Injectable() +export class UpdateManyQueryFactory { + constructor( + private readonly fieldsStringFactory: FieldsStringFactory, + private readonly argsAliasFactory: ArgsAliasFactory, + ) {} + + create< + Record extends IRecord = IRecord, + Filter extends RecordFilter = RecordFilter, + >( + args: UpdateManyResolverArgs, + options: WorkspaceQueryBuilderOptions, + ) { + const fieldsString = this.fieldsStringFactory.create( + options.info, + options.fieldMetadataCollection, + ); + + const computedArgs = this.argsAliasFactory.create( + args, + options.fieldMetadataCollection, + ); + + return ` + mutation { + update${options.targetTableName}Collection( + set: ${stringifyWithoutKeyQuote(computedArgs.data)}, + filter: ${stringifyWithoutKeyQuote(args.filter)}, + ) { + affectedCount + records { + ${fieldsString} + } + } + }`; + } +} diff --git a/server/src/workspace/workspace-query-builder/workspace-query-builder.factory.ts b/server/src/workspace/workspace-query-builder/workspace-query-builder.factory.ts index 74c43bf48..0f706edbf 100644 --- a/server/src/workspace/workspace-query-builder/workspace-query-builder.factory.ts +++ b/server/src/workspace/workspace-query-builder/workspace-query-builder.factory.ts @@ -12,6 +12,8 @@ import { CreateManyResolverArgs, UpdateOneResolverArgs, DeleteOneResolverArgs, + UpdateManyResolverArgs, + DeleteManyResolverArgs, } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { FindManyQueryFactory } from './factories/find-many-query.factory'; @@ -19,6 +21,8 @@ import { FindOneQueryFactory } from './factories/find-one-query.factory'; import { CreateManyQueryFactory } from './factories/create-many-query.factory'; import { UpdateOneQueryFactory } from './factories/update-one-query.factory'; import { DeleteOneQueryFactory } from './factories/delete-one-query.factory'; +import { UpdateManyQueryFactory } from './factories/update-many-query.factory'; +import { DeleteManyQueryFactory } from './factories/delete-many-query.factory'; @Injectable() export class WorkspaceQueryBuilderFactory { @@ -30,6 +34,8 @@ export class WorkspaceQueryBuilderFactory { private readonly createManyQueryFactory: CreateManyQueryFactory, private readonly updateOneQueryFactory: UpdateOneQueryFactory, private readonly deleteOneQueryFactory: DeleteOneQueryFactory, + private readonly updateManyQueryFactory: UpdateManyQueryFactory, + private readonly deleteManyQueryFactory: DeleteManyQueryFactory, ) {} findMany< @@ -69,4 +75,21 @@ export class WorkspaceQueryBuilderFactory { ): string { return this.deleteOneQueryFactory.create(args, options); } + + updateMany< + Record extends IRecord = IRecord, + Filter extends RecordFilter = RecordFilter, + >( + args: UpdateManyResolverArgs, + options: WorkspaceQueryBuilderOptions, + ): string { + return this.updateManyQueryFactory.create(args, options); + } + + deleteMany( + args: DeleteManyResolverArgs, + options: WorkspaceQueryBuilderOptions, + ): string { + return this.deleteManyQueryFactory.create(args, options); + } } diff --git a/server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts b/server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts index 1961a7ca2..e0956029b 100644 --- a/server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts +++ b/server/src/workspace/workspace-query-runner/workspace-query-runner.service.ts @@ -9,9 +9,11 @@ import { import { CreateManyResolverArgs, CreateOneResolverArgs, + DeleteManyResolverArgs, DeleteOneResolverArgs, FindManyResolverArgs, FindOneResolverArgs, + UpdateManyResolverArgs, UpdateOneResolverArgs, } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -127,6 +129,43 @@ export class WorkspaceQueryRunnerService { )?.records?.[0]; } + async updateMany( + args: UpdateManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { workspaceId, targetTableName } = options; + + const query = this.workspaceQueryBuilderFactory.updateMany(args, options); + + const result = await this.execute(query, workspaceId); + + return this.parseResult>( + result, + targetTableName, + 'update', + )?.records; + } + + async deleteMany< + Record extends IRecord = IRecord, + Filter extends RecordFilter = RecordFilter, + >( + args: DeleteManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { workspaceId, targetTableName } = options; + + const query = this.workspaceQueryBuilderFactory.deleteMany(args, options); + + const result = await this.execute(query, workspaceId); + + return this.parseResult>( + result, + targetTableName, + 'deleteFrom', + )?.records; + } + private async execute( query: string, workspaceId: string, diff --git a/server/src/workspace/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/server/src/workspace/workspace-resolver-builder/factories/delete-many-resolver.factory.ts new file mode 100644 index 000000000..48e9c20d8 --- /dev/null +++ b/server/src/workspace/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; + +import { + DeleteManyResolverArgs, + Resolver, +} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { WorkspaceSchemaBuilderContext } from 'src/workspace/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { WorkspaceResolverBuilderFactoryInterface } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; + +import { WorkspaceQueryRunnerService } from 'src/workspace/workspace-query-runner/workspace-query-runner.service'; + +@Injectable() +export class DeleteManyResolverFactory + implements WorkspaceResolverBuilderFactoryInterface +{ + public static methodName = 'deleteMany' as const; + + constructor( + private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + ) {} + + create( + context: WorkspaceSchemaBuilderContext, + ): Resolver { + const internalContext = context; + + return (_source, args, context, info) => { + return this.workspaceQueryRunnerService.deleteMany(args, { + targetTableName: internalContext.targetTableName, + workspaceId: internalContext.workspaceId, + info, + fieldMetadataCollection: internalContext.fieldMetadataCollection, + }); + }; + } +} diff --git a/server/src/workspace/workspace-resolver-builder/factories/factories.ts b/server/src/workspace/workspace-resolver-builder/factories/factories.ts index ec02b4892..5b77030d8 100644 --- a/server/src/workspace/workspace-resolver-builder/factories/factories.ts +++ b/server/src/workspace/workspace-resolver-builder/factories/factories.ts @@ -1,9 +1,12 @@ +import { UpdateManyResolverFactory } from 'src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory'; + import { FindManyResolverFactory } from './find-many-resolver.factory'; import { FindOneResolverFactory } from './find-one-resolver.factory'; import { CreateManyResolverFactory } from './create-many-resolver.factory'; import { CreateOneResolverFactory } from './create-one-resolver.factory'; import { UpdateOneResolverFactory } from './update-one-resolver.factory'; import { DeleteOneResolverFactory } from './delete-one-resolver.factory'; +import { DeleteManyResolverFactory } from './delete-many-resolver.factory'; export const workspaceResolverBuilderFactories = [ FindManyResolverFactory, @@ -12,6 +15,8 @@ export const workspaceResolverBuilderFactories = [ CreateOneResolverFactory, UpdateOneResolverFactory, DeleteOneResolverFactory, + UpdateManyResolverFactory, + DeleteManyResolverFactory, ]; export const workspaceResolverBuilderMethodNames = { @@ -24,5 +29,7 @@ export const workspaceResolverBuilderMethodNames = { CreateOneResolverFactory.methodName, UpdateOneResolverFactory.methodName, DeleteOneResolverFactory.methodName, + UpdateManyResolverFactory.methodName, + DeleteManyResolverFactory.methodName, ], } as const; diff --git a/server/src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/server/src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory.ts new file mode 100644 index 000000000..0e10642d4 --- /dev/null +++ b/server/src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; + +import { + Resolver, + UpdateManyResolverArgs, +} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { WorkspaceSchemaBuilderContext } from 'src/workspace/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { WorkspaceResolverBuilderFactoryInterface } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; + +import { WorkspaceQueryRunnerService } from 'src/workspace/workspace-query-runner/workspace-query-runner.service'; + +@Injectable() +export class UpdateManyResolverFactory + implements WorkspaceResolverBuilderFactoryInterface +{ + public static methodName = 'updateMany' as const; + + constructor( + private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + ) {} + + create( + context: WorkspaceSchemaBuilderContext, + ): Resolver { + const internalContext = context; + + return (_source, args, context, info) => { + return this.workspaceQueryRunnerService.updateMany(args, { + targetTableName: internalContext.targetTableName, + workspaceId: internalContext.workspaceId, + info, + fieldMetadataCollection: internalContext.fieldMetadataCollection, + }); + }; + } +} diff --git a/server/src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts b/server/src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts index 5b13f96cf..cf25531a8 100644 --- a/server/src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts +++ b/server/src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts @@ -39,10 +39,22 @@ export interface UpdateOneResolverArgs { data: Data; } +export interface UpdateManyResolverArgs< + Data extends Record = Record, + Filter = any, +> { + filter: Filter; + data: Data; +} + export interface DeleteOneResolverArgs { id: string; } +export interface DeleteManyResolverArgs { + filter: Filter; +} + export type WorkspaceResolverBuilderQueryMethodNames = (typeof workspaceResolverBuilderMethodNames.queries)[number]; diff --git a/server/src/workspace/workspace-resolver-builder/workspace-resolver.factory.ts b/server/src/workspace/workspace-resolver-builder/workspace-resolver.factory.ts index 826e4e63d..7b67ce211 100644 --- a/server/src/workspace/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/server/src/workspace/workspace-resolver-builder/workspace-resolver.factory.ts @@ -5,6 +5,8 @@ import { IResolvers } from '@graphql-tools/utils'; import { ObjectMetadataInterface } from 'src/workspace/workspace-schema-builder/interfaces/object-metadata.interface'; import { getResolverName } from 'src/workspace/utils/get-resolver-name.util'; +import { UpdateManyResolverFactory } from 'src/workspace/workspace-resolver-builder/factories/update-many-resolver.factory'; +import { DeleteManyResolverFactory } from 'src/workspace/workspace-resolver-builder/factories/delete-many-resolver.factory'; import { FindManyResolverFactory } from './factories/find-many-resolver.factory'; import { FindOneResolverFactory } from './factories/find-one-resolver.factory'; @@ -29,6 +31,8 @@ export class WorkspaceResolverFactory { private readonly createOneResolverFactory: CreateOneResolverFactory, private readonly updateOneResolverFactory: UpdateOneResolverFactory, private readonly deleteOneResolverFactory: DeleteOneResolverFactory, + private readonly updateManyResolverFactory: UpdateManyResolverFactory, + private readonly deleteManyResolverFactory: DeleteManyResolverFactory, ) {} async create( @@ -46,6 +50,8 @@ export class WorkspaceResolverFactory { ['createOne', this.createOneResolverFactory], ['updateOne', this.updateOneResolverFactory], ['deleteOne', this.deleteOneResolverFactory], + ['updateMany', this.updateManyResolverFactory], + ['deleteMany', this.deleteManyResolverFactory], ]); const resolvers: IResolvers = { Query: {}, diff --git a/server/src/workspace/workspace-schema-builder/factories/root-type.factory.ts b/server/src/workspace/workspace-schema-builder/factories/root-type.factory.ts index 4fe78208c..4f595e7bf 100644 --- a/server/src/workspace/workspace-schema-builder/factories/root-type.factory.ts +++ b/server/src/workspace/workspace-schema-builder/factories/root-type.factory.ts @@ -9,6 +9,7 @@ import { ObjectMetadataInterface } from 'src/workspace/workspace-schema-builder/ import { TypeDefinitionsStorage } from 'src/workspace/workspace-schema-builder/storages/type-definitions.storage'; import { getResolverName } from 'src/workspace/utils/get-resolver-name.util'; import { getResolverArgs } from 'src/workspace/workspace-schema-builder/utils/get-resolver-args.util'; +import { TypeMapperService } from 'src/workspace/workspace-schema-builder/services/type-mapper.service'; import { ArgsFactory } from './args.factory'; import { ObjectTypeDefinitionKind } from './object-type-definition.factory'; @@ -25,6 +26,7 @@ export class RootTypeFactory { constructor( private readonly typeDefinitionsStorage: TypeDefinitionsStorage, + private readonly typeMapperService: TypeMapperService, private readonly argsFactory: ArgsFactory, ) {} @@ -70,7 +72,7 @@ export class RootTypeFactory { for (const methodName of workspaceResolverMethodNames) { const name = getResolverName(objectMetadata, methodName); const args = getResolverArgs(methodName); - const outputType = this.typeDefinitionsStorage.getObjectTypeByKey( + const objectType = this.typeDefinitionsStorage.getObjectTypeByKey( objectMetadata.id, methodName === 'findMany' ? ObjectTypeDefinitionKind.Connection @@ -84,7 +86,7 @@ export class RootTypeFactory { options, ); - if (!outputType) { + if (!objectType) { this.logger.error( `Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`, { @@ -99,6 +101,12 @@ export class RootTypeFactory { ); } + const outputType = this.typeMapperService.mapToGqlType(objectType, { + isArray: ['updateMany', 'deleteMany', 'createMany'].includes( + methodName, + ), + }); + fieldConfigMap[name] = { type: outputType, args: argsType, diff --git a/server/src/workspace/workspace-schema-builder/utils/get-resolver-args.util.ts b/server/src/workspace/workspace-schema-builder/utils/get-resolver-args.util.ts index deb7f460d..e4bb3fb86 100644 --- a/server/src/workspace/workspace-schema-builder/utils/get-resolver-args.util.ts +++ b/server/src/workspace/workspace-schema-builder/utils/get-resolver-args.util.ts @@ -36,6 +36,7 @@ export const getResolverArgs = ( }, }; case 'findOne': + case 'deleteMany': return { filter: { kind: InputTypeDefinitionKind.Filter, @@ -75,6 +76,17 @@ export const getResolverArgs = ( isNullable: false, }, }; + case 'updateMany': + return { + data: { + kind: InputTypeDefinitionKind.Update, + isNullable: false, + }, + filter: { + kind: InputTypeDefinitionKind.Filter, + isNullable: false, + }, + }; default: throw new Error(`Unknown resolver type: ${type}`); }