diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts index 377d7d6a61..f6964f796c 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts @@ -14,7 +14,6 @@ import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-que import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResolverService< @@ -31,14 +30,9 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index abd5acc6de..bc2a0fb613 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -12,7 +12,6 @@ import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/c import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseResolverService< @@ -29,14 +28,9 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index 13f65996eb..9543bbdaa1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -28,7 +28,6 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService< @@ -111,14 +110,9 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - graphqlQueryParser.applyFilterToBuilder( duplicateRecordsQueryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, duplicateConditions, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index dcb529fdd2..7e3cfc8acf 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -30,7 +30,6 @@ import { import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { isDefined } from 'src/utils/is-defined'; @Injectable() @@ -57,14 +56,9 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve let appliedFilters = executionArgs.args.filter ?? ({} as ObjectRecordFilter); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( aggregateQueryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, appliedFilters, ); @@ -99,14 +93,14 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, appliedFilters, ); executionArgs.graphqlQueryParser.applyOrderToBuilder( queryBuilder, orderByWithIdCondition, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, isForwardPagination, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index 8f11c354fe..5150e1a2f1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -23,7 +23,6 @@ import { WorkspaceQueryRunnerExceptionCode, } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService< @@ -40,14 +39,9 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter ?? ({} as ObjectRecordFilter), ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts index 8ed96cf1ca..5b73e80def 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts @@ -14,7 +14,6 @@ import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-que import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService< @@ -31,14 +30,9 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index b607e86672..f9d0e58971 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -20,7 +20,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { isDefined } from 'src/utils/is-defined'; @Injectable() @@ -64,14 +63,9 @@ export class GraphqlQuerySearchResolverService extends GraphqlQueryBaseResolverS objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter ?? ({} as ObjectRecordFilter), ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index 1f194f18db..a8b730c4a8 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -15,7 +15,6 @@ import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService< @@ -32,14 +31,9 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); - const tableName = computeTableName( - objectMetadataItemWithFieldMaps.nameSingular, - objectMetadataItemWithFieldMaps.isCustom, - ); - executionArgs.graphqlQueryParser.applyFilterToBuilder( queryBuilder, - tableName, + objectMetadataItemWithFieldMaps.nameSingular, executionArgs.args.filter, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts index ddcf2ff282..69e86b8164 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory.ts @@ -7,6 +7,7 @@ import { import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs, + CreateOneResolverArgs, FindDuplicatesResolverArgs, FindManyResolverArgs, FindOneResolverArgs, @@ -43,6 +44,19 @@ export class QueryRunnerArgsFactory { ); switch (resolverArgsType) { + case ResolverArgsType.CreateOne: + return { + ...args, + data: await this.overrideDataByFieldMetadata( + (args as CreateOneResolverArgs).data, + options, + fieldMetadataMapByNameByName, + { + argIndex: 0, + shouldBackfillPosition, + }, + ), + } satisfies CreateOneResolverArgs; case ResolverArgsType.CreateMany: return { ...args, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator.ts index d879794186..83b10fa918 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator.ts @@ -3,8 +3,8 @@ import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants'; import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { WORKSPACE_QUERY_HOOK_METADATA } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.constants'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; +import { WORKSPACE_QUERY_HOOK_METADATA } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.constants'; export type WorkspaceQueryHookKey = `${string}.${WorkspaceResolverBuilderMethodNames}`; diff --git a/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts b/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts index d69cc69a23..4e7b9006bb 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts @@ -1,12 +1,13 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { CreatedByPreQueryHook } from 'src/engine/core-modules/actor/query-hooks/created-by.pre-query-hook'; +import { CreatedByCreateManyPreQueryHook } from 'src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook'; +import { CreatedByCreateOnePreQueryHook } from 'src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @Module({ imports: [TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata')], - providers: [CreatedByPreQueryHook], - exports: [CreatedByPreQueryHook], + providers: [CreatedByCreateManyPreQueryHook, CreatedByCreateOnePreQueryHook], + exports: [CreatedByCreateManyPreQueryHook, CreatedByCreateOnePreQueryHook], }) export class ActorModule {} diff --git a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.pre-query-hook.ts b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts similarity index 96% rename from packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.pre-query-hook.ts rename to packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts index 0ef8bab573..8160552261 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.pre-query-hook.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts @@ -27,8 +27,10 @@ type CustomWorkspaceItem = Omit< }; @WorkspaceQueryHook(`*.createMany`) -export class CreatedByPreQueryHook implements WorkspaceQueryHookInstance { - private readonly logger = new Logger(CreatedByPreQueryHook.name); +export class CreatedByCreateManyPreQueryHook + implements WorkspaceQueryHookInstance +{ + private readonly logger = new Logger(CreatedByCreateManyPreQueryHook.name); constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, diff --git a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts new file mode 100644 index 0000000000..03ba6e648d --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts @@ -0,0 +1,117 @@ +import { Logger } from '@nestjs/common/services/logger.service'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + +import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { buildCreatedByFromWorkspaceMember } from 'src/engine/core-modules/actor/utils/build-created-by-from-workspace-member.util'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + ActorMetadata, + FieldActorSource, +} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; + +type CustomWorkspaceItem = Omit< + CustomWorkspaceEntity, + 'createdAt' | 'updatedAt' +> & { + createdAt: string; + updatedAt: string; +}; + +@WorkspaceQueryHook(`*.createOne`) +export class CreatedByCreateOnePreQueryHook + implements WorkspaceQueryHookInstance +{ + private readonly logger = new Logger(CreatedByCreateOnePreQueryHook.name); + + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + @InjectRepository(FieldMetadataEntity, 'metadata') + private readonly fieldMetadataRepository: Repository, + ) {} + + async execute( + authContext: AuthContext, + objectName: string, + payload: CreateOneResolverArgs, + ): Promise> { + let createdBy: ActorMetadata | null = null; + + // TODO: Once all objects have it, we can remove this check + const createdByFieldMetadata = await this.fieldMetadataRepository.findOne({ + where: { + object: { + nameSingular: objectName, + }, + name: 'createdBy', + workspaceId: authContext.workspace.id, + }, + }); + + if (!createdByFieldMetadata) { + return payload; + } + + // If user is logged in, we use the workspace member + if (authContext.workspaceMemberId && authContext.user) { + createdBy = buildCreatedByFromWorkspaceMember( + authContext.workspaceMemberId, + authContext.user, + ); + // TODO: remove that code once we have the workspace member id in all tokens + } else if (authContext.user) { + this.logger.warn("User doesn't have a workspace member id in the token"); + const workspaceMemberRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + authContext.workspace.id, + 'workspaceMember', + ); + + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { + userId: authContext.user?.id, + }, + }); + + if (!workspaceMember) { + throw new Error( + `Workspace member can't be found for user ${authContext.user.id}`, + ); + } + + createdBy = { + source: FieldActorSource.MANUAL, + workspaceMemberId: workspaceMember.id, + name: `${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`, + }; + } + + if (authContext.apiKey) { + createdBy = { + source: FieldActorSource.API, + name: authContext.apiKey.name, + }; + } + + // Front-end can fill the source field + if ( + createdBy && + (!payload.data.createdBy || !payload.data.createdBy?.name) + ) { + payload.data.createdBy = { + ...createdBy, + source: payload.data.createdBy?.source ?? createdBy.source, + }; + } + + return payload; + } +}