mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 05:37:56 +00:00
feat: add missing updateMany and deleteMany resolvers on flexible backend (#2758)
* feat: add missing updateMany and deleteMany resolvers on flexible backend Co-authored-by: v1b3m <vibenjamin6@gmail.com> * Refactor according to review Co-authored-by: v1b3m <vibenjamin6@gmail.com> * Update return types for `createMany`, `updateMany` and `deleteMany` Co-authored-by: v1b3m <vibenjamin6@gmail.com> --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: v1b3m <vibenjamin6@gmail.com>
This commit is contained in:
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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<Record, Filter>,
|
||||
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}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
}
|
||||
@@ -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<Record, Filter>,
|
||||
options: WorkspaceQueryBuilderOptions,
|
||||
): string {
|
||||
return this.updateManyQueryFactory.create(args, options);
|
||||
}
|
||||
|
||||
deleteMany<Filter extends RecordFilter = RecordFilter>(
|
||||
args: DeleteManyResolverArgs<Filter>,
|
||||
options: WorkspaceQueryBuilderOptions,
|
||||
): string {
|
||||
return this.deleteManyQueryFactory.create(args, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Record extends IRecord = IRecord>(
|
||||
args: UpdateManyResolverArgs<Record>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record[] | undefined> {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
|
||||
const query = this.workspaceQueryBuilderFactory.updateMany(args, options);
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
'update',
|
||||
)?.records;
|
||||
}
|
||||
|
||||
async deleteMany<
|
||||
Record extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
>(
|
||||
args: DeleteManyResolverArgs<Filter>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record[] | undefined> {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
|
||||
const query = this.workspaceQueryBuilderFactory.deleteMany(args, options);
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
return this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
'deleteFrom',
|
||||
)?.records;
|
||||
}
|
||||
|
||||
private async execute(
|
||||
query: string,
|
||||
workspaceId: string,
|
||||
|
||||
@@ -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<DeleteManyResolverArgs> {
|
||||
const internalContext = context;
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.deleteMany(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<UpdateManyResolverArgs> {
|
||||
const internalContext = context;
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.updateMany(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,22 @@ export interface UpdateOneResolverArgs<Data extends Record = Record> {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface UpdateManyResolverArgs<
|
||||
Data extends Record = Record,
|
||||
Filter = any,
|
||||
> {
|
||||
filter: Filter;
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface DeleteOneResolverArgs {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface DeleteManyResolverArgs<Filter = any> {
|
||||
filter: Filter;
|
||||
}
|
||||
|
||||
export type WorkspaceResolverBuilderQueryMethodNames =
|
||||
(typeof workspaceResolverBuilderMethodNames.queries)[number];
|
||||
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user