[permissions] Add read field permission check layer (part 1) (#13376)
In this PR, behind a feature flag, we add a permission layer check based on the read permission. It is done by computing a map of an object's fields, where keys are the column names and values the fieldMetadata id, making them comparable to the restricted fields ids list stored in the permission cache. For mutations (create, update, delete, destroy), we need to check the read permission on the returned field, as they may differ from the updated field. The write field permission will be tackled in a different PR.
This commit is contained in:
@ -22,6 +22,7 @@ const mockedWorkspaceUpdateQueryBuilder = {
|
||||
execute: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ affected: 1, raw: [], generatedMaps: [] }),
|
||||
returning: jest.fn().mockReturnThis(),
|
||||
})),
|
||||
execute: jest
|
||||
.fn()
|
||||
@ -38,6 +39,7 @@ jest.mock('../repository/workspace-select-query-builder', () => ({
|
||||
.fn()
|
||||
.mockResolvedValue({ affected: 1, raw: [], generatedMaps: [] }),
|
||||
setFindOptions: jest.fn().mockReturnThis(),
|
||||
returning: jest.fn().mockReturnThis(),
|
||||
update: jest.fn().mockReturnValue(mockedWorkspaceUpdateQueryBuilder),
|
||||
insert: jest.fn().mockReturnThis(),
|
||||
})),
|
||||
@ -269,17 +271,20 @@ describe('WorkspaceEntityManager', () => {
|
||||
{ reload: false },
|
||||
mockPermissionOptions,
|
||||
);
|
||||
expect(entityManager['validatePermissions']).toHaveBeenCalledWith(
|
||||
'test-entity',
|
||||
'update',
|
||||
mockPermissionOptions,
|
||||
);
|
||||
expect(entityManager['validatePermissions']).toHaveBeenCalledWith({
|
||||
target: 'test-entity',
|
||||
operationType: 'update',
|
||||
permissionOptions: mockPermissionOptions,
|
||||
selectedColumns: [],
|
||||
});
|
||||
expect(validateOperationIsPermittedOrThrow).toHaveBeenCalledWith({
|
||||
entityName: 'test-entity',
|
||||
operationType: 'update',
|
||||
objectMetadataMaps: mockInternalContext.objectMetadataMaps,
|
||||
objectRecordsPermissions:
|
||||
mockPermissionOptions.objectRecordsPermissions,
|
||||
selectedColumns: [],
|
||||
allFieldsSelected: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -299,17 +304,20 @@ describe('WorkspaceEntityManager', () => {
|
||||
describe('Other Methods', () => {
|
||||
it('should call validatePermissions and validateOperationIsPermittedOrThrow for clear', async () => {
|
||||
await entityManager.clear('test-entity', mockPermissionOptions);
|
||||
expect(entityManager['validatePermissions']).toHaveBeenCalledWith(
|
||||
'test-entity',
|
||||
'delete',
|
||||
mockPermissionOptions,
|
||||
);
|
||||
expect(entityManager['validatePermissions']).toHaveBeenCalledWith({
|
||||
target: 'test-entity',
|
||||
operationType: 'delete',
|
||||
permissionOptions: mockPermissionOptions,
|
||||
selectedColumns: [],
|
||||
});
|
||||
expect(validateOperationIsPermittedOrThrow).toHaveBeenCalledWith({
|
||||
entityName: 'test-entity',
|
||||
operationType: 'delete',
|
||||
objectMetadataMaps: mockInternalContext.objectMetadataMaps,
|
||||
objectRecordsPermissions:
|
||||
mockPermissionOptions.objectRecordsPermissions,
|
||||
selectedColumns: [],
|
||||
allFieldsSelected: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -164,6 +164,8 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
options?.objectRecordsPermissions ?? {},
|
||||
this.internalContext,
|
||||
options?.shouldBypassPermissionChecks ?? false,
|
||||
undefined,
|
||||
this.getFeatureFlagMap(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -172,6 +174,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
entity:
|
||||
| QueryDeepPartialEntityWithRelationConnect<Entity>
|
||||
| QueryDeepPartialEntityWithRelationConnect<Entity>[],
|
||||
selectedColumns: string[] = [],
|
||||
permissionOptions?: PermissionOptions,
|
||||
): Promise<InsertResult> {
|
||||
const entityArray = Array.isArray(entity) ? entity : [entity];
|
||||
@ -191,6 +194,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
.insert()
|
||||
.into(target)
|
||||
.values(connectedEntities)
|
||||
.returning(selectedColumns)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@ -204,6 +208,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
selectedColumns: string[] = [],
|
||||
): Promise<InsertResult> {
|
||||
const metadata = this.connection.getMetadata(target);
|
||||
let options;
|
||||
@ -257,6 +262,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
this.connection.driver.supportedUpsertTypes[0],
|
||||
},
|
||||
)
|
||||
.returning(selectedColumns)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@ -274,6 +280,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
| unknown,
|
||||
partialEntity: QueryDeepPartialEntity<Entity>,
|
||||
permissionOptions?: PermissionOptions,
|
||||
selectedColumns: string[] = [],
|
||||
): Promise<UpdateResult> {
|
||||
const metadata = this.connection.getMetadata(target);
|
||||
|
||||
@ -304,6 +311,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
.update()
|
||||
.set(partialEntity)
|
||||
.whereInIds(criteria)
|
||||
.returning(selectedColumns)
|
||||
.execute();
|
||||
} else {
|
||||
return this.createQueryBuilder(
|
||||
@ -315,6 +323,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
.update()
|
||||
.set(partialEntity)
|
||||
.where(criteria)
|
||||
.returning(selectedColumns)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
@ -325,6 +334,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
permissionOptions?: PermissionOptions,
|
||||
selectedColumns: string[] = [],
|
||||
): Promise<UpdateResult> {
|
||||
const metadata = this.connection.getMetadata(target);
|
||||
const column = metadata.findColumnWithPropertyPath(propertyPath);
|
||||
@ -351,17 +361,24 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
.update(target as QueryDeepPartialEntity<Entity>)
|
||||
.set(values)
|
||||
.where(criteria)
|
||||
.returning(selectedColumns)
|
||||
.execute();
|
||||
}
|
||||
|
||||
validatePermissions<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity> | Entity,
|
||||
operationType: OperationType,
|
||||
validatePermissions<Entity extends ObjectLiteral>({
|
||||
target,
|
||||
operationType,
|
||||
permissionOptions,
|
||||
selectedColumns,
|
||||
}: {
|
||||
target: EntityTarget<Entity> | Entity;
|
||||
operationType: OperationType;
|
||||
permissionOptions?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
): void {
|
||||
};
|
||||
selectedColumns: string[];
|
||||
}): void {
|
||||
if (permissionOptions?.shouldBypassPermissionChecks === true) {
|
||||
return;
|
||||
}
|
||||
@ -377,6 +394,8 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
objectRecordsPermissions:
|
||||
permissionOptions?.objectRecordsPermissions ?? {},
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
selectedColumns,
|
||||
allFieldsSelected: false,
|
||||
});
|
||||
}
|
||||
|
||||
@ -858,7 +877,12 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
entityClass: EntityTarget<Entity>,
|
||||
permissionOptions?: PermissionOptions,
|
||||
): Promise<void> {
|
||||
this.validatePermissions(entityClass, 'delete', permissionOptions);
|
||||
this.validatePermissions({
|
||||
target: entityClass,
|
||||
operationType: 'delete',
|
||||
permissionOptions,
|
||||
selectedColumns: [], // TODO
|
||||
});
|
||||
|
||||
return super.clear(entityClass);
|
||||
}
|
||||
@ -910,6 +934,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
permissionOptions?: PermissionOptions,
|
||||
selectedColumns: string[] = [],
|
||||
): Promise<UpdateResult> {
|
||||
const metadata = this.connection.getMetadata(target);
|
||||
const column = metadata.findColumnWithPropertyPath(propertyPath);
|
||||
@ -935,6 +960,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
.update(target as QueryDeepPartialEntity<Entity>)
|
||||
.set(values)
|
||||
.where(criteria)
|
||||
.returning(selectedColumns)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@ -1029,11 +1055,12 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
? maybeOptionsOrMaybePermissionOptions
|
||||
: permissionOptions;
|
||||
|
||||
this.validatePermissions(
|
||||
targetOrEntity,
|
||||
'update',
|
||||
permissionOptionsFromArgs,
|
||||
);
|
||||
this.validatePermissions({
|
||||
target: targetOrEntity,
|
||||
operationType: 'update',
|
||||
permissionOptions: permissionOptionsFromArgs,
|
||||
selectedColumns: [], // TODO
|
||||
});
|
||||
|
||||
let target =
|
||||
arguments.length > 1 &&
|
||||
@ -1170,11 +1197,12 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
? (maybeOptionsOrMaybePermissionOptions as PermissionOptions)
|
||||
: permissionOptions;
|
||||
|
||||
this.validatePermissions(
|
||||
targetOrEntity,
|
||||
'delete',
|
||||
permissionOptionsFromArgs,
|
||||
);
|
||||
this.validatePermissions({
|
||||
target: targetOrEntity,
|
||||
operationType: 'delete',
|
||||
permissionOptions: permissionOptionsFromArgs,
|
||||
selectedColumns: [], // TODO
|
||||
});
|
||||
|
||||
const target =
|
||||
arguments.length > 1 &&
|
||||
@ -1281,11 +1309,12 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
? (maybeOptionsOrMaybePermissionOptions as PermissionOptions)
|
||||
: permissionOptions;
|
||||
|
||||
this.validatePermissions(
|
||||
targetOrEntityOrEntities,
|
||||
'soft-delete',
|
||||
permissionOptionsFromArgs,
|
||||
);
|
||||
this.validatePermissions({
|
||||
target: targetOrEntityOrEntities,
|
||||
operationType: 'soft-delete',
|
||||
permissionOptions: permissionOptionsFromArgs,
|
||||
selectedColumns: [], // TODO
|
||||
});
|
||||
|
||||
let target =
|
||||
arguments.length > 1 &&
|
||||
@ -1387,11 +1416,12 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
? (maybeOptionsOrMaybePermissionOptions as PermissionOptions)
|
||||
: permissionOptions;
|
||||
|
||||
this.validatePermissions(
|
||||
targetOrEntityOrEntities,
|
||||
'restore',
|
||||
permissionOptionsFromArgs,
|
||||
);
|
||||
this.validatePermissions({
|
||||
target: targetOrEntityOrEntities,
|
||||
operationType: 'restore',
|
||||
permissionOptions: permissionOptionsFromArgs,
|
||||
selectedColumns: [], // TODO
|
||||
});
|
||||
|
||||
let target =
|
||||
arguments.length > 1 &&
|
||||
|
||||
@ -268,6 +268,7 @@ describe('WorkspaceRepository', () => {
|
||||
expect(mockEntityManager.insert).toHaveBeenCalledWith(
|
||||
'test-entity',
|
||||
{ id: 'test-id' },
|
||||
undefined,
|
||||
{
|
||||
shouldBypassPermissionChecks: false,
|
||||
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||
@ -294,6 +295,7 @@ describe('WorkspaceRepository', () => {
|
||||
shouldBypassPermissionChecks: false,
|
||||
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||
},
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -319,6 +321,7 @@ describe('WorkspaceRepository', () => {
|
||||
shouldBypassPermissionChecks: false,
|
||||
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -362,6 +365,7 @@ describe('WorkspaceRepository', () => {
|
||||
shouldBypassPermissionChecks: false,
|
||||
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import {
|
||||
ObjectRecordsPermissions,
|
||||
RestrictedFields,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { QueryExpressionMap } from 'typeorm/query-builder/QueryExpressionMap';
|
||||
|
||||
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||
import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { getFieldMetadataIdForColumnNameMap } from 'src/engine/twenty-orm/utils/get-field-metadata-id-for-column-name.util';
|
||||
|
||||
const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
|
||||
const mainEntity = expressionMap.aliases[0].metadata.name;
|
||||
@ -33,11 +40,17 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
operationType,
|
||||
objectRecordsPermissions,
|
||||
objectMetadataMaps,
|
||||
selectedColumns,
|
||||
isFieldPermissionsEnabled,
|
||||
allFieldsSelected,
|
||||
}: {
|
||||
entityName: string;
|
||||
operationType: OperationType;
|
||||
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
selectedColumns: string[];
|
||||
isFieldPermissionsEnabled?: boolean;
|
||||
allFieldsSelected: boolean;
|
||||
}) => {
|
||||
const objectMetadataIdForEntity =
|
||||
objectMetadataMaps.idByNameSingular[entityName];
|
||||
@ -64,6 +77,10 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldMetadataIdForColumnNameMap = isFieldPermissionsEnabled
|
||||
? getFieldMetadataIdForColumnNameMap(objectMetadata)
|
||||
: {};
|
||||
|
||||
const permissionsForEntity =
|
||||
objectRecordsPermissions[objectMetadataIdForEntity];
|
||||
|
||||
@ -75,6 +92,15 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (isFieldPermissionsEnabled) {
|
||||
validateReadFieldPermissionOrThrow({
|
||||
restrictedFields: permissionsForEntity.restrictedFields,
|
||||
selectedColumns,
|
||||
fieldMetadataIdForColumnNameMap,
|
||||
allFieldsSelected,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'insert':
|
||||
case 'update':
|
||||
@ -84,6 +110,14 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (isFieldPermissionsEnabled) {
|
||||
validateReadFieldPermissionOrThrow({
|
||||
restrictedFields: permissionsForEntity.restrictedFields,
|
||||
selectedColumns,
|
||||
fieldMetadataIdForColumnNameMap,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
if (!permissionsForEntity?.canDestroy) {
|
||||
@ -92,6 +126,14 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (isFieldPermissionsEnabled) {
|
||||
validateReadFieldPermissionOrThrow({
|
||||
restrictedFields: permissionsForEntity.restrictedFields,
|
||||
selectedColumns,
|
||||
fieldMetadataIdForColumnNameMap,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'restore':
|
||||
case 'soft-delete':
|
||||
@ -101,6 +143,14 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
if (isFieldPermissionsEnabled) {
|
||||
validateReadFieldPermissionOrThrow({
|
||||
restrictedFields: permissionsForEntity.restrictedFields,
|
||||
selectedColumns,
|
||||
fieldMetadataIdForColumnNameMap,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new PermissionsException(
|
||||
@ -108,14 +158,25 @@ export const validateOperationIsPermittedOrThrow = ({
|
||||
PermissionsExceptionCode.UNKNOWN_OPERATION_NAME,
|
||||
);
|
||||
}
|
||||
|
||||
if (isEmpty(permissionsForEntity.restrictedFields)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export const validateQueryIsPermittedOrThrow = (
|
||||
expressionMap: QueryExpressionMap,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
) => {
|
||||
export const validateQueryIsPermittedOrThrow = ({
|
||||
expressionMap,
|
||||
objectRecordsPermissions,
|
||||
objectMetadataMaps,
|
||||
shouldBypassPermissionChecks,
|
||||
isFieldPermissionsEnabled,
|
||||
}: {
|
||||
expressionMap: QueryExpressionMap;
|
||||
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
shouldBypassPermissionChecks: boolean;
|
||||
isFieldPermissionsEnabled?: boolean;
|
||||
}) => {
|
||||
if (shouldBypassPermissionChecks) {
|
||||
return;
|
||||
}
|
||||
@ -123,10 +184,119 @@ export const validateQueryIsPermittedOrThrow = (
|
||||
const { mainEntity, operationType } =
|
||||
getTargetEntityAndOperationType(expressionMap);
|
||||
|
||||
const allFieldsSelected = expressionMap.selects.some(
|
||||
(select) => select.selection === mainEntity,
|
||||
);
|
||||
|
||||
let selectedColumns: string[] = [];
|
||||
|
||||
if (isFieldPermissionsEnabled) {
|
||||
selectedColumns = getSelectedColumnsFromExpressionMap({
|
||||
operationType,
|
||||
expressionMap,
|
||||
allFieldsSelected,
|
||||
});
|
||||
}
|
||||
|
||||
validateOperationIsPermittedOrThrow({
|
||||
entityName: mainEntity,
|
||||
operationType: operationType as OperationType,
|
||||
objectRecordsPermissions,
|
||||
objectMetadataMaps,
|
||||
selectedColumns: selectedColumns,
|
||||
isFieldPermissionsEnabled,
|
||||
allFieldsSelected,
|
||||
});
|
||||
};
|
||||
|
||||
const validateReadFieldPermissionOrThrow = ({
|
||||
restrictedFields,
|
||||
selectedColumns,
|
||||
fieldMetadataIdForColumnNameMap,
|
||||
allFieldsSelected,
|
||||
}: {
|
||||
restrictedFields: RestrictedFields;
|
||||
selectedColumns: string[];
|
||||
fieldMetadataIdForColumnNameMap: Record<string, string>;
|
||||
allFieldsSelected?: boolean;
|
||||
}) => {
|
||||
if (isEmpty(restrictedFields)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (allFieldsSelected) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
|
||||
for (const column of selectedColumns) {
|
||||
const fieldMetadataId = fieldMetadataIdForColumnNameMap[column];
|
||||
|
||||
if (!fieldMetadataId) {
|
||||
throw new InternalServerError(
|
||||
`Field metadata id not found for column name ${column}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (restrictedFields[fieldMetadataId]?.canRead === false) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedColumnsFromExpressionMap = ({
|
||||
operationType,
|
||||
expressionMap,
|
||||
allFieldsSelected,
|
||||
}: {
|
||||
operationType: string;
|
||||
expressionMap: QueryExpressionMap;
|
||||
allFieldsSelected: boolean;
|
||||
}) => {
|
||||
let selectedColumns: string[] = [];
|
||||
|
||||
if (
|
||||
['update', 'insert', 'delete', 'soft-delete', 'restore'].includes(
|
||||
operationType,
|
||||
)
|
||||
) {
|
||||
if (isEmpty(expressionMap.returning)) {
|
||||
throw new InternalServerError(
|
||||
'Returning columns are not set for update query',
|
||||
);
|
||||
}
|
||||
selectedColumns = [expressionMap.returning].flat();
|
||||
} else if (!allFieldsSelected) {
|
||||
selectedColumns = getSelectedColumnsFromExpressionMapSelects(
|
||||
expressionMap.selects,
|
||||
);
|
||||
}
|
||||
|
||||
return selectedColumns;
|
||||
};
|
||||
|
||||
const getSelectedColumnsFromExpressionMapSelects = (
|
||||
selects: { selection: string }[],
|
||||
) => {
|
||||
return selects
|
||||
?.map((select) => {
|
||||
const columnsFromAggregateExpression =
|
||||
ProcessAggregateHelper.extractColumnNamesFromAggregateExpression(
|
||||
select.selection,
|
||||
);
|
||||
|
||||
if (columnsFromAggregateExpression) {
|
||||
return columnsFromAggregateExpression;
|
||||
}
|
||||
|
||||
const parts = select.selection.split('.');
|
||||
|
||||
return parts[parts.length - 1];
|
||||
})
|
||||
.flat();
|
||||
};
|
||||
|
||||
@ -8,10 +8,12 @@ import {
|
||||
} from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@ -30,18 +32,21 @@ export class WorkspaceDeleteQueryBuilder<
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
private featureFlagMap?: FeatureFlagMap;
|
||||
constructor(
|
||||
queryBuilder: DeleteQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
featureFlagMap?: FeatureFlagMap,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -57,12 +62,14 @@ export class WorkspaceDeleteQueryBuilder<
|
||||
}
|
||||
|
||||
override async execute(): Promise<DeleteResult & { generatedMaps: T[] }> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
validateQueryIsPermittedOrThrow({
|
||||
expressionMap: this.expressionMap,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
isFieldPermissionsEnabled:
|
||||
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
|
||||
});
|
||||
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
|
||||
@ -7,10 +7,12 @@ import {
|
||||
} from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@ -31,6 +33,7 @@ export class WorkspaceInsertQueryBuilder<
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
private featureFlagMap?: FeatureFlagMap;
|
||||
|
||||
constructor(
|
||||
queryBuilder: InsertQueryBuilder<T>,
|
||||
@ -38,12 +41,14 @@ export class WorkspaceInsertQueryBuilder<
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
featureFlagMap?: FeatureFlagMap,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -55,6 +60,7 @@ export class WorkspaceInsertQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
) as this;
|
||||
}
|
||||
|
||||
@ -74,12 +80,14 @@ export class WorkspaceInsertQueryBuilder<
|
||||
}
|
||||
|
||||
override async execute(): Promise<InsertResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
validateQueryIsPermittedOrThrow({
|
||||
expressionMap: this.expressionMap,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
isFieldPermissionsEnabled:
|
||||
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
|
||||
});
|
||||
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
|
||||
@ -2,9 +2,11 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { EntityTarget, ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -28,18 +30,21 @@ export class WorkspaceSelectQueryBuilder<
|
||||
shouldBypassPermissionChecks: boolean;
|
||||
internalContext: WorkspaceInternalContext;
|
||||
authContext?: AuthContext;
|
||||
featureFlagMap?: FeatureFlagMap;
|
||||
constructor(
|
||||
queryBuilder: SelectQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
featureFlagMap?: FeatureFlagMap,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
}
|
||||
|
||||
getFindOptions() {
|
||||
@ -55,6 +60,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
) as this;
|
||||
}
|
||||
|
||||
@ -204,6 +210,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -226,6 +233,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -238,6 +246,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -250,6 +259,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -262,6 +272,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -273,12 +284,16 @@ export class WorkspaceSelectQueryBuilder<
|
||||
}
|
||||
|
||||
private validatePermissions(): void {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
const isFieldPermissionsEnabled =
|
||||
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED];
|
||||
|
||||
validateQueryIsPermittedOrThrow({
|
||||
expressionMap: this.expressionMap,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
isFieldPermissionsEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
|
||||
@ -7,10 +7,12 @@ import {
|
||||
} from 'typeorm';
|
||||
import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@ -29,6 +31,7 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
private featureFlagMap?: FeatureFlagMap;
|
||||
|
||||
constructor(
|
||||
queryBuilder: SoftDeleteQueryBuilder<T>,
|
||||
@ -36,12 +39,14 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
featureFlagMap?: FeatureFlagMap,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -57,12 +62,14 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
||||
}
|
||||
|
||||
override async execute(): Promise<UpdateResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
validateQueryIsPermittedOrThrow({
|
||||
expressionMap: this.expressionMap,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
isFieldPermissionsEnabled:
|
||||
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
|
||||
});
|
||||
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
|
||||
@ -7,10 +7,12 @@ import {
|
||||
} from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
@ -30,18 +32,21 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
private featureFlagMap?: FeatureFlagMap;
|
||||
constructor(
|
||||
queryBuilder: UpdateQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
featureFlagMap?: FeatureFlagMap,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -53,16 +58,19 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
) as this;
|
||||
}
|
||||
|
||||
override async execute(): Promise<UpdateResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
validateQueryIsPermittedOrThrow({
|
||||
expressionMap: this.expressionMap,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
isFieldPermissionsEnabled:
|
||||
this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED],
|
||||
});
|
||||
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
@ -77,6 +85,7 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
this.internalContext,
|
||||
true,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
|
||||
eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
|
||||
@ -109,9 +118,15 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
authContext: this.authContext,
|
||||
});
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return {
|
||||
raw: result.raw,
|
||||
generatedMaps: formattedAfter,
|
||||
generatedMaps: formattedResult,
|
||||
affected: result.affected,
|
||||
};
|
||||
}
|
||||
|
||||
@ -81,6 +81,7 @@ export class WorkspaceRepository<
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
}
|
||||
|
||||
@ -538,6 +539,7 @@ export class WorkspaceRepository<
|
||||
| QueryDeepPartialEntityWithRelationConnect<T>
|
||||
| QueryDeepPartialEntityWithRelationConnect<T>[],
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
selectedColumns?: string[],
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -546,7 +548,12 @@ export class WorkspaceRepository<
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
};
|
||||
|
||||
return manager.insert(this.target, entity, permissionOptions);
|
||||
return manager.insert(
|
||||
this.target,
|
||||
entity,
|
||||
selectedColumns,
|
||||
permissionOptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -565,6 +572,7 @@ export class WorkspaceRepository<
|
||||
| FindOptionsWhere<T>,
|
||||
partialEntity: QueryDeepPartialEntity<T>,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
selectedColumns?: string[],
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -582,6 +590,7 @@ export class WorkspaceRepository<
|
||||
criteria,
|
||||
partialEntity,
|
||||
permissionOptions,
|
||||
selectedColumns,
|
||||
);
|
||||
}
|
||||
|
||||
@ -589,6 +598,7 @@ export class WorkspaceRepository<
|
||||
entityOrEntities: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||
conflictPathsOrOptions: string[] | UpsertOptions<T>,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
selectedColumns: string[] = [],
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -602,6 +612,7 @@ export class WorkspaceRepository<
|
||||
entityOrEntities,
|
||||
conflictPathsOrOptions,
|
||||
permissionOptions,
|
||||
selectedColumns,
|
||||
);
|
||||
|
||||
return {
|
||||
@ -777,6 +788,7 @@ export class WorkspaceRepository<
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
selectedColumns?: string[],
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedConditions = await this.transformOptions({
|
||||
@ -794,6 +806,7 @@ export class WorkspaceRepository<
|
||||
propertyPath,
|
||||
value,
|
||||
permissionOptions,
|
||||
selectedColumns,
|
||||
);
|
||||
}
|
||||
|
||||
@ -802,6 +815,7 @@ export class WorkspaceRepository<
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
selectedColumns?: string[],
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedConditions = await this.transformOptions({
|
||||
@ -819,6 +833,7 @@ export class WorkspaceRepository<
|
||||
propertyPath,
|
||||
value,
|
||||
permissionOptions,
|
||||
selectedColumns,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import {
|
||||
computeColumnName,
|
||||
computeCompositeColumnName,
|
||||
} 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 { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
export function getFieldMetadataIdForColumnNameMap(
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
) {
|
||||
const columnNameToFieldMetadataIdMap: Record<string, string> = {};
|
||||
|
||||
for (const [fieldMetadataId, fieldMetadata] of Object.entries(
|
||||
objectMetadataItemWithFieldMaps.fieldsById,
|
||||
)) {
|
||||
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
|
||||
|
||||
if (!compositeType) {
|
||||
throw new InternalServerError(
|
||||
`Composite type not found for field metadata type ${fieldMetadata.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
compositeType.properties.forEach((compositeProperty) => {
|
||||
const columnName = computeCompositeColumnName(
|
||||
fieldMetadata.name,
|
||||
compositeProperty,
|
||||
);
|
||||
|
||||
columnNameToFieldMetadataIdMap[columnName] = fieldMetadataId;
|
||||
});
|
||||
} else {
|
||||
const columnName = computeColumnName(fieldMetadata, {
|
||||
isForeignKey: fieldMetadata.type === FieldMetadataType.RELATION,
|
||||
});
|
||||
|
||||
columnNameToFieldMetadataIdMap[columnName] = fieldMetadataId;
|
||||
}
|
||||
}
|
||||
|
||||
return columnNameToFieldMetadataIdMap;
|
||||
}
|
||||
Reference in New Issue
Block a user