mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 12:02:10 +03:00
Fix record creation (#8664)
## Context Now that each operation has its own resolver, we need to make sure they all map to query arg getters. CreateOne was not properly mapped to the position getter which made record creation fail because "position: first" was not properly converted to a float. Also fixing queries with custom object where we were wrongly using the table name instead of entity name
This commit is contained in:
parent
1c04b2b0b7
commit
04c359a5dc
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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}`;
|
||||
|
@ -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 {}
|
||||
|
@ -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,
|
@ -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<FieldMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
authContext: AuthContext,
|
||||
objectName: string,
|
||||
payload: CreateOneResolverArgs<CustomWorkspaceItem>,
|
||||
): Promise<CreateOneResolverArgs<CustomWorkspaceItem>> {
|
||||
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<WorkspaceMemberWorkspaceEntity>(
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user