Files
twenty/packages/twenty-server/src/engine/twenty-orm/utils/process-field-metadata-for-column-name-mapping.util.ts
T
Marie 9957057d1f [permissions] Adapt field permissions to connect + createMany (#13655)
In this PR we adapt field permission to two things

1. Connect: the recent insertion of the connect feature introduces the
possibility to have "connect" objects in typeORM's expressionMap's
valueSet, where we previously only had column names (if i followed
correctly). For instance for a person object that a N - 1 relationship
to company, person will have both companyId and company as possible
valueSet keys for the upsert. We need to reflect that in our
`getColumnNameToFieldMetadataIdMap` util that returns a map containing
every possible value we could encounter in valueSet. In an attempt to
tie this to the schema generation where this is introduced, I created
the shallow util extractGraphQLRelationFieldNames (probably ill-named -
im willing to update the name if @etiennejouan has a better idea?) to
remind us that these two are linked.

2. CreateMany: When calling query builders methods directly on custom
object (like we do in graphql-create-many-resolver), we need to be
careful to call them with a selection of readable fields. We had a
debate on whether we should compute this selection containing all
readable fields by default under the hood when no selected fields are
indicated. I am still not 100% convinced as I think it should remain the
caller's responsibility, but this case reminds us that it could easily
be forgotten by developers - although it is all the more the case as we
don't have seeds yet that help us realize that ([PR on the
way](https://github.com/twentyhq/twenty/pull/13646)).
2025-08-06 08:31:52 +00:00

101 lines
3.6 KiB
TypeScript

import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { FieldMetadataRelationSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import {
PermissionsException,
PermissionsExceptionCode,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export type ColumnNameProcessor = {
processCompositeField: ({
fieldMetadataId,
fieldMetadata,
compositeType,
}: {
fieldMetadataId: string;
fieldMetadata: FieldMetadataEntity;
compositeType: CompositeType;
}) => void;
processRelationField: ({
fieldMetadataId,
fieldMetadata,
joinColumnName,
connectFieldName,
}: {
fieldMetadataId: string;
fieldMetadata: FieldMetadataEntity;
joinColumnName: string;
connectFieldName?: string;
}) => void;
processSimpleField: ({
fieldMetadataId,
fieldMetadata,
columnName,
}: {
fieldMetadataId: string;
fieldMetadata: FieldMetadataEntity;
columnName: string;
}) => void;
};
export function processFieldMetadataForColumnNameMapping(
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
processor: ColumnNameProcessor,
) {
for (const [fieldMetadataId, fieldMetadata] of Object.entries(
objectMetadataItemWithFieldMaps.fieldsById,
)) {
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
if (!compositeType) {
throw new PermissionsException(
`Composite type not found for field metadata type ${fieldMetadata.type}`,
PermissionsExceptionCode.COMPOSITE_TYPE_NOT_FOUND,
);
}
processor.processCompositeField({
fieldMetadataId,
fieldMetadata,
compositeType,
});
} else {
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
const fieldMetadataSettings =
fieldMetadata.settings as FieldMetadataRelationSettings;
if (fieldMetadataSettings?.relationType === RelationType.ONE_TO_MANY) {
continue;
}
const { joinColumnName, fieldMetadataName } =
extractGraphQLRelationFieldNames(fieldMetadata);
processor.processRelationField({
fieldMetadataId,
fieldMetadata,
joinColumnName,
connectFieldName: fieldMetadataName,
});
} else {
const columnName = computeColumnName(fieldMetadata);
processor.processSimpleField({
fieldMetadataId,
fieldMetadata,
columnName,
});
}
}
}
}