Fix relation field unknown target object (#13129)
Fixes https://github.com/twentyhq/twenty/issues/12867 Issue: when you have a variable `toto` which is: `Record<string, MyType>` and you do toto['xxx'], this will be typed as `MyType` instead of `MyType | undefined` Solutions: - activate `noUncheckedIndexedAccess` check in tsconfig, this is the preferred solution but will take time to get there (this raises 600+ errors) - use a Map: cf https://github.com/twentyhq/twenty/pull/13125/files - set the type to Partial<Record<string, MyType>>. Drawback is that when you do Object.values(toto), you'll get `Array<MyType | undefined>`. Hence why we have to filter these behind <img width="1512" alt="image" src="https://github.com/user-attachments/assets/d0a0bfed-c441-4e53-84c2-2da98ccbcf50" />
This commit is contained in:
@ -19,9 +19,9 @@ import { PersonQueryResultGetterHandler } from 'src/engine/api/graphql/workspace
|
||||
import { WorkspaceMemberQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler';
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
// TODO: find a way to prevent conflict between handlers executing logic on object relations
|
||||
// And this factory that is also executing logic on object relations
|
||||
@ -121,6 +121,10 @@ export class QueryResultGettersFactory {
|
||||
): Promise<ObjectRecord> {
|
||||
const objectMetadataMapItem = objectMetadataMaps.byId[objectMetadataItemId];
|
||||
|
||||
if (!isDefined(objectMetadataMapItem)) {
|
||||
throw new Error('Object metadata map item is not defined');
|
||||
}
|
||||
|
||||
const handler = this.getHandler(objectMetadataMapItem.nameSingular);
|
||||
|
||||
const relationFields = Object.keys(record)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { IResolvers } from '@graphql-tools/utils';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { DeleteManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory';
|
||||
import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory';
|
||||
@ -75,7 +76,9 @@ export class WorkspaceResolverFactory {
|
||||
Mutation: {},
|
||||
};
|
||||
|
||||
for (const objectMetadata of Object.values(objectMetadataMaps.byId)) {
|
||||
for (const objectMetadata of Object.values(objectMetadataMaps.byId).filter(
|
||||
isDefined,
|
||||
)) {
|
||||
// Generate query resolvers
|
||||
for (const methodName of workspaceResolverBuilderMethods.queries) {
|
||||
const resolverName = getResolverName(objectMetadata, methodName);
|
||||
|
||||
@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
import { GraphQLSchema, printSchema } from 'graphql';
|
||||
import { gql } from 'graphql-tag';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
|
||||
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
|
||||
@ -56,13 +57,13 @@ export class WorkspaceSchemaFactory {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataCollection = Object.values(objectMetadataMaps.byId).map(
|
||||
(objectMetadataItem) => ({
|
||||
const objectMetadataCollection = Object.values(objectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((objectMetadataItem) => ({
|
||||
...objectMetadataItem,
|
||||
fields: Object.values(objectMetadataItem.fieldsById),
|
||||
indexes: objectMetadataItem.indexMetadatas,
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
||||
// Get typeDefs from cache
|
||||
let typeDefs = await this.workspaceCacheStorageService.getGraphQLTypeDefs(
|
||||
|
||||
@ -171,6 +171,12 @@ export abstract class RestApiBaseHandler {
|
||||
objectMetadata.objectMetadataMaps.byId[
|
||||
field.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadata)) {
|
||||
throw new BadRequestException(
|
||||
`Object metadata relation target not found for relation creation payload`,
|
||||
);
|
||||
}
|
||||
const depth2Relations = this.getRelations({
|
||||
objectMetadata: {
|
||||
objectMetadataMaps: objectMetadata.objectMetadataMaps,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
@ -58,6 +59,10 @@ export const mapFieldMetadataToGraphqlQuery = (
|
||||
const relationMetadataItem =
|
||||
objectMetadataMaps.byId[targetObjectMetadataId];
|
||||
|
||||
if (!isDefined(relationMetadataItem)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${field.name}
|
||||
{
|
||||
id
|
||||
|
||||
@ -5,7 +5,7 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
|
||||
export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMaps[] =
|
||||
[
|
||||
{
|
||||
id: '',
|
||||
id: '20202020-8dec-43d5-b2ff-6eef05095bec',
|
||||
standardId: '',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
@ -52,7 +52,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
||||
fieldIdByJoinColumnName: {},
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
id: '20202020-c03c-45d6-a4b0-04afe1357c5c',
|
||||
standardId: '',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
@ -112,7 +112,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
||||
fieldIdByJoinColumnName: {},
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
id: '20202020-3d75-4aab-bacd-ee176c5f63ca',
|
||||
standardId: '',
|
||||
nameSingular: 'regular-custom-object',
|
||||
namePlural: 'regular-custom-objects',
|
||||
@ -172,7 +172,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
||||
fieldIdByJoinColumnName: {},
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
id: '20202020-540c-4397-b872-2a90ea2fb809',
|
||||
standardId: '',
|
||||
nameSingular: 'non-searchable-object',
|
||||
namePlural: 'non-searchable-objects',
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Args, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
|
||||
@ -10,12 +12,16 @@ import { SearchService } from 'src/engine/core-modules/search/services/search.se
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@Resolver()
|
||||
@UseFilters(SearchApiExceptionFilter, PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
export class SearchResolver {
|
||||
constructor(private readonly searchService: SearchService) {}
|
||||
constructor(
|
||||
private readonly searchService: SearchService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
) {}
|
||||
|
||||
@Query(() => SearchResultConnectionDTO)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@ -31,12 +37,16 @@ export class SearchResolver {
|
||||
after,
|
||||
}: SearchArgs,
|
||||
) {
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
await this.searchService.getObjectMetadataItemWithFieldMaps(workspace);
|
||||
const objectMetadataMaps =
|
||||
await this.workspaceCacheStorageService.getObjectMetadataMapsOrThrow(
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
const filteredObjectMetadataItems =
|
||||
this.searchService.filterObjectMetadataItems({
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataItemWithFieldMaps: Object.values(
|
||||
objectMetadataMaps.byId,
|
||||
).filter(isDefined),
|
||||
includedObjectNameSingulars: includedObjectNameSingulars ?? [],
|
||||
excludedObjectNameSingulars: excludedObjectNameSingulars ?? [],
|
||||
});
|
||||
|
||||
@ -29,13 +29,11 @@ import {
|
||||
} from 'src/engine/core-modules/search/exceptions/search.exception';
|
||||
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/search/types/records-with-object-metadata-item';
|
||||
import { formatSearchTerms } from 'src/engine/core-modules/search/utils/format-search-terms';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
type LastRanks = { tsRankCD: number; tsRank: number };
|
||||
|
||||
@ -51,18 +49,8 @@ export class SearchService {
|
||||
constructor(
|
||||
private readonly twentyORMManager: TwentyORMManager,
|
||||
private readonly fileService: FileService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
) {}
|
||||
|
||||
async getObjectMetadataItemWithFieldMaps(workspace: Workspace) {
|
||||
const objectMetadataMaps =
|
||||
await this.workspaceCacheStorageService.getObjectMetadataMapsOrThrow(
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return Object.values(objectMetadataMaps.byId);
|
||||
}
|
||||
|
||||
async getAllRecordsWithObjectMetadataItems({
|
||||
objectMetadataItemWithFieldMaps,
|
||||
includedObjectNameSingulars,
|
||||
|
||||
@ -114,8 +114,14 @@ export class DataloaderService {
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const indexMetadataCollection = objectMetadataIds.map((id) =>
|
||||
Object.values(objectMetadataMaps.byId[id].indexMetadatas).map(
|
||||
const indexMetadataCollection = objectMetadataIds.map((id) => {
|
||||
const objectMetadata = objectMetadataMaps.byId[id];
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.values(objectMetadata.indexMetadatas).map(
|
||||
(indexMetadata) => {
|
||||
return {
|
||||
...indexMetadata,
|
||||
@ -127,8 +133,8 @@ export class DataloaderService {
|
||||
workspaceId: workspaceId,
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
return indexMetadataCollection;
|
||||
},
|
||||
@ -148,8 +154,14 @@ export class DataloaderService {
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const fieldMetadataCollection = objectMetadataIds.map((id) =>
|
||||
Object.values(objectMetadataMaps.byId[id].fieldsById).map(
|
||||
const fieldMetadataCollection = objectMetadataIds.map((id) => {
|
||||
const objectMetadata = objectMetadataMaps.byId[id];
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.values(objectMetadata.fieldsById).map(
|
||||
// TODO: fix this as we should merge FieldMetadataEntity and FieldMetadataInterface
|
||||
(fieldMetadata) => {
|
||||
const overridesFieldToCompute = [
|
||||
@ -182,8 +194,8 @@ export class DataloaderService {
|
||||
...overrides,
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
return fieldMetadataCollection;
|
||||
},
|
||||
@ -207,9 +219,13 @@ export class DataloaderService {
|
||||
objectMetadata: { id: objectMetadataId },
|
||||
indexMetadata: { id: indexMetadataId },
|
||||
}) => {
|
||||
const indexMetadataEntity = objectMetadataMaps.byId[
|
||||
objectMetadataId
|
||||
].indexMetadatas.find(
|
||||
const objectMetadata = objectMetadataMaps.byId[objectMetadataId];
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const indexMetadataEntity = objectMetadata.indexMetadatas.find(
|
||||
(indexMetadata) => indexMetadata.id === indexMetadataId,
|
||||
);
|
||||
|
||||
|
||||
@ -166,6 +166,13 @@ export class FieldMetadataRelationService {
|
||||
relationCreationPayload.targetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(objectMetadataTarget)) {
|
||||
throw new FieldMetadataException(
|
||||
`Object metadata relation target not found for relation creation payload`,
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
);
|
||||
}
|
||||
|
||||
validateFieldNameAvailabilityOrThrow(
|
||||
computedMetadataNameFromLabel,
|
||||
objectMetadataTarget,
|
||||
|
||||
@ -107,7 +107,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
|
||||
let existingFieldMetadata: FieldMetadataInterface | undefined;
|
||||
|
||||
for (const objectMetadataItem of Object.values(objectMetadataMaps.byId)) {
|
||||
for (const objectMetadataItem of Object.values(
|
||||
objectMetadataMaps.byId,
|
||||
).filter(isDefined)) {
|
||||
const fieldMetadata = objectMetadataItem.fieldsById[id];
|
||||
|
||||
if (fieldMetadata) {
|
||||
@ -126,6 +128,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
objectMetadataMaps.byId[existingFieldMetadata.objectMetadataId];
|
||||
|
||||
if (!isDefined(objectMetadataItemWithFieldMaps)) {
|
||||
throw new FieldMetadataException(
|
||||
'Object metadata does not exist',
|
||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
@ -703,7 +712,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
isRemoteCreation,
|
||||
}: {
|
||||
createdFieldMetadataItems: FieldMetadataEntity[];
|
||||
objectMetadataMap: Record<string, ObjectMetadataItemWithFieldMaps>;
|
||||
objectMetadataMap: ObjectMetadataMaps['byId'];
|
||||
isRemoteCreation: boolean;
|
||||
}): Promise<WorkspaceMigrationTableAction[]> {
|
||||
if (isRemoteCreation) {
|
||||
@ -726,10 +735,18 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
}
|
||||
}
|
||||
|
||||
const objectMetadata =
|
||||
objectMetadataMap[createdFieldMetadata.objectMetadataId];
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
throw new FieldMetadataException(
|
||||
'Object metadata does not exist',
|
||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
migrationActions.push({
|
||||
name: computeObjectTargetTable(
|
||||
objectMetadataMap[createdFieldMetadata.objectMetadataId],
|
||||
),
|
||||
name: computeObjectTargetTable(objectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: this.workspaceMigrationFactory.createColumnActions(
|
||||
WorkspaceMigrationColumnActionType.CREATE,
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { QueryRunner, Repository } from 'typeorm';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
@ -82,10 +82,12 @@ export class ObjectMetadataFieldRelationService {
|
||||
relationObjectMetadataStandardId: string;
|
||||
queryRunner?: QueryRunner;
|
||||
}) {
|
||||
const targetObjectMetadata = Object.values(objectMetadataMaps.byId).find(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.standardId === relationObjectMetadataStandardId,
|
||||
);
|
||||
const targetObjectMetadata = Object.values(objectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.find(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.standardId === relationObjectMetadataStandardId,
|
||||
);
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new Error(
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@ -126,7 +126,7 @@ export class FieldPermissionService {
|
||||
role,
|
||||
}: {
|
||||
fieldPermission: UpsertFieldPermissionsInput['fieldPermissions'][0];
|
||||
objectMetadataMapsById: Record<string, ObjectMetadataItemWithFieldMaps>;
|
||||
objectMetadataMapsById: ObjectMetadataMaps['byId'];
|
||||
rolesPermissions: ObjectRecordsPermissionsByRoleId;
|
||||
role: RoleEntity;
|
||||
}) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
export type ObjectMetadataMaps = {
|
||||
byId: Record<string, ObjectMetadataItemWithFieldMaps>;
|
||||
idByNameSingular: Record<string, string>;
|
||||
byId: Partial<Record<string, ObjectMetadataItemWithFieldMaps>>;
|
||||
idByNameSingular: Partial<Record<string, string>>;
|
||||
};
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
@ -5,7 +7,9 @@ export const getObjectMetadataMapItemByNamePlural = (
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
namePlural: string,
|
||||
): ObjectMetadataItemWithFieldMaps | undefined => {
|
||||
const objectMetadataItems = Object.values(objectMetadataMaps.byId);
|
||||
const objectMetadataItems = Object.values(objectMetadataMaps.byId).filter(
|
||||
isDefined,
|
||||
);
|
||||
|
||||
return objectMetadataItems.find(
|
||||
(objectMetadata) => objectMetadata.namePlural === namePlural,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
@ -5,7 +7,11 @@ export const getObjectMetadataMapItemByNameSingular = (
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
nameSingular: string,
|
||||
): ObjectMetadataItemWithFieldMaps | undefined => {
|
||||
return objectMetadataMaps.byId[
|
||||
objectMetadataMaps.idByNameSingular[nameSingular]
|
||||
];
|
||||
const objectMetadataId = objectMetadataMaps.idByNameSingular[nameSingular];
|
||||
|
||||
if (!isNonEmptyString(objectMetadataId)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return objectMetadataMaps.byId[objectMetadataId];
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
@ -19,14 +20,16 @@ export const validatesNoOtherObjectWithSameNameExistsOrThrows = ({
|
||||
existingObjectMetadataId,
|
||||
objectMetadataMaps,
|
||||
}: ValidateNoOtherObjectWithSameNameExistsOrThrowsParams) => {
|
||||
const objectAlreadyExists = Object.values(objectMetadataMaps.byId).find(
|
||||
(objectMetadata) =>
|
||||
(objectMetadata.nameSingular === objectMetadataNameSingular ||
|
||||
objectMetadata.namePlural === objectMetadataNamePlural ||
|
||||
objectMetadata.nameSingular === objectMetadataNamePlural ||
|
||||
objectMetadata.namePlural === objectMetadataNameSingular) &&
|
||||
objectMetadata.id !== existingObjectMetadataId,
|
||||
);
|
||||
const objectAlreadyExists = Object.values(objectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.find(
|
||||
(objectMetadata) =>
|
||||
(objectMetadata.nameSingular === objectMetadataNameSingular ||
|
||||
objectMetadata.namePlural === objectMetadataNamePlural ||
|
||||
objectMetadata.nameSingular === objectMetadataNamePlural ||
|
||||
objectMetadata.namePlural === objectMetadataNameSingular) &&
|
||||
objectMetadata.id !== existingObjectMetadataId,
|
||||
);
|
||||
|
||||
if (objectAlreadyExists) {
|
||||
throw new ObjectMetadataException(
|
||||
|
||||
@ -166,15 +166,16 @@ export class WorkspaceDatasourceFactory {
|
||||
);
|
||||
} else {
|
||||
const entitySchemas = await Promise.all(
|
||||
Object.values(cachedObjectMetadataMaps.byId).map(
|
||||
(objectMetadata) =>
|
||||
Object.values(cachedObjectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((objectMetadata) =>
|
||||
this.entitySchemaFactory.create(
|
||||
workspaceId,
|
||||
dataSourceMetadataVersion,
|
||||
objectMetadata,
|
||||
cachedObjectMetadataMaps,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await this.workspaceCacheStorageService.setORMEntitySchema(
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { QueryExpressionMap } from 'typeorm/query-builder/QueryExpressionMap';
|
||||
|
||||
import {
|
||||
@ -40,8 +42,23 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
const objectMetadataIdForEntity =
|
||||
objectMetadataMaps.idByNameSingular[entityName];
|
||||
|
||||
const objectMetadataIsSystem =
|
||||
objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true;
|
||||
if (!isNonEmptyString(objectMetadataIdForEntity)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata = objectMetadataMaps.byId[objectMetadataIdForEntity];
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataIsSystem = objectMetadata.isSystem === true;
|
||||
|
||||
if (objectMetadataIsSystem) {
|
||||
return;
|
||||
|
||||
@ -30,10 +30,11 @@ export const generateFakeFormResponse = async ({
|
||||
formFieldMetadata?.settings?.objectName,
|
||||
);
|
||||
|
||||
if (!objectMetadataItemWithFieldsMaps)
|
||||
if (!isDefined(objectMetadataItemWithFieldsMaps)) {
|
||||
throw new Error(
|
||||
`Object metadata not found for object name ${formFieldMetadata?.settings?.objectName}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
[formFieldMetadata.name]: {
|
||||
|
||||
@ -43,6 +43,10 @@ export const generateObjectRecordFields = ({
|
||||
field.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadata)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[field.name] = {
|
||||
isLeaf: false,
|
||||
icon: field.icon,
|
||||
|
||||
@ -196,7 +196,11 @@ export class DatabaseEventTriggerListener {
|
||||
}
|
||||
|
||||
const relatedObjectMetadataNameSingular =
|
||||
objectMetadataMaps.byId[relatedObjectMetadataId].nameSingular;
|
||||
objectMetadataMaps.byId[relatedObjectMetadataId]?.nameSingular;
|
||||
|
||||
if (!isDefined(relatedObjectMetadataNameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
|
||||
@ -60,7 +60,7 @@ exports[`Field metadata relation creation should fail relation when targetObject
|
||||
"exceptionEventId": "mocked-exception-id",
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Cannot read properties of undefined (reading 'fieldsById')",
|
||||
"message": "Object metadata relation target not found for relation creation payload",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user