Migrate object permission to syncable entity (#18609)

Closes [#2223](https://github.com/twentyhq/core-team-issues/issues/2223)
This commit is contained in:
Abdul Rahman
2026-03-18 22:02:18 +05:30
committed by GitHub
parent 8ccaf635bc
commit 6d4d1a97bc
86 changed files with 2475 additions and 424 deletions
@@ -0,0 +1,10 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_OBJECT_PERMISSION_EDITABLE_PROPERTIES = [
'roleId',
'objectMetadataId',
'canReadObjectRecords',
'canUpdateObjectRecords',
'canSoftDeleteObjectRecords',
'canDestroyObjectRecords',
] as const satisfies MetadataEntityPropertyName<'objectPermission'>[];
@@ -0,0 +1,86 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { type FlatObjectPermissionMaps } from 'src/engine/metadata-modules/flat-object-permission/types/flat-object-permission-maps.type';
import { fromObjectPermissionEntityToFlatObjectPermission } from 'src/engine/metadata-modules/flat-object-permission/utils/from-object-permission-entity-to-flat-object-permission.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
@Injectable()
@WorkspaceCache('flatObjectPermissionMaps')
export class WorkspaceFlatObjectPermissionMapCacheService extends WorkspaceCacheProvider<FlatObjectPermissionMaps> {
constructor(
@InjectRepository(ObjectPermissionEntity)
private readonly objectPermissionRepository: Repository<ObjectPermissionEntity>,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
@InjectRepository(RoleEntity)
private readonly roleRepository: Repository<RoleEntity>,
@InjectRepository(ObjectMetadataEntity)
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
) {
super();
}
async computeForCache(
workspaceId: string,
): Promise<FlatObjectPermissionMaps> {
const [objectPermissions, applications, roles, objectMetadatas] =
await Promise.all([
this.objectPermissionRepository.find({
where: { workspaceId },
withDeleted: true,
}),
this.applicationRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.roleRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
this.objectMetadataRepository.find({
where: { workspaceId },
select: ['id', 'universalIdentifier'],
withDeleted: true,
}),
]);
const applicationIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(applications);
const roleIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(roles);
const objectMetadataIdToUniversalIdentifierMap =
createIdToUniversalIdentifierMap(objectMetadatas);
const flatObjectPermissionMaps = createEmptyFlatEntityMaps();
for (const objectPermissionEntity of objectPermissions) {
const flatObjectPermission =
fromObjectPermissionEntityToFlatObjectPermission({
entity: objectPermissionEntity,
applicationIdToUniversalIdentifierMap,
roleIdToUniversalIdentifierMap,
objectMetadataIdToUniversalIdentifierMap,
});
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity: flatObjectPermission,
flatEntityMapsToMutate: flatObjectPermissionMaps,
});
}
return flatObjectPermissionMaps;
}
}
@@ -0,0 +1,4 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatObjectPermission } from 'src/engine/metadata-modules/flat-object-permission/types/flat-object-permission.type';
export type FlatObjectPermissionMaps = FlatEntityMaps<FlatObjectPermission>;
@@ -0,0 +1,4 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
export type FlatObjectPermission = FlatEntityFrom<ObjectPermissionEntity>;
@@ -0,0 +1,54 @@
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { type CreateObjectPermissionInput } from 'src/engine/metadata-modules/object-permission/dtos/create-object-permission.input';
import { type UniversalFlatObjectPermission } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-permission.type';
export const fromCreateObjectPermissionInputToUniversalFlatObjectPermission = ({
createObjectPermissionInput,
flatApplication,
flatRoleMaps,
flatObjectMetadataMaps,
}: {
createObjectPermissionInput: CreateObjectPermissionInput;
flatApplication: FlatApplication;
} & Pick<
AllFlatEntityMaps,
'flatRoleMaps' | 'flatObjectMetadataMaps'
>): UniversalFlatObjectPermission & { id: string } => {
const {
roleId,
objectMetadataId,
canReadObjectRecords,
canUpdateObjectRecords,
canSoftDeleteObjectRecords,
canDestroyObjectRecords,
universalIdentifier,
} = createObjectPermissionInput;
const now = new Date().toISOString();
const { roleUniversalIdentifier, objectMetadataUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'objectPermission',
foreignKeyValues: { roleId, objectMetadataId },
flatEntityMaps: {
flatRoleMaps,
flatObjectMetadataMaps,
},
});
return {
id: v4(),
universalIdentifier: universalIdentifier ?? v4(),
applicationUniversalIdentifier: flatApplication.universalIdentifier,
roleUniversalIdentifier,
objectMetadataUniversalIdentifier,
canReadObjectRecords: canReadObjectRecords ?? undefined,
canUpdateObjectRecords: canUpdateObjectRecords ?? undefined,
canSoftDeleteObjectRecords: canSoftDeleteObjectRecords ?? undefined,
canDestroyObjectRecords: canDestroyObjectRecords ?? undefined,
createdAt: now,
updatedAt: now,
};
};
@@ -0,0 +1,67 @@
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util';
import { type FlatObjectPermission } from 'src/engine/metadata-modules/flat-object-permission/types/flat-object-permission.type';
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
export const fromObjectPermissionEntityToFlatObjectPermission = ({
entity: objectPermissionEntity,
applicationIdToUniversalIdentifierMap,
roleIdToUniversalIdentifierMap,
objectMetadataIdToUniversalIdentifierMap,
}: FromEntityToFlatEntityArgs<'objectPermission'>): FlatObjectPermission => {
const objectPermissionEntityWithoutRelations = removePropertiesFromRecord(
objectPermissionEntity,
getMetadataEntityRelationProperties('objectPermission'),
);
const applicationUniversalIdentifier =
applicationIdToUniversalIdentifierMap.get(
objectPermissionEntity.applicationId,
);
if (!isDefined(applicationUniversalIdentifier)) {
throw new FlatEntityMapsException(
`Application with id ${objectPermissionEntity.applicationId} not found for objectPermission ${objectPermissionEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const roleUniversalIdentifier = roleIdToUniversalIdentifierMap.get(
objectPermissionEntity.roleId,
);
if (!isDefined(roleUniversalIdentifier)) {
throw new FlatEntityMapsException(
`Role with id ${objectPermissionEntity.roleId} not found for objectPermission ${objectPermissionEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const objectMetadataUniversalIdentifier =
objectMetadataIdToUniversalIdentifierMap.get(
objectPermissionEntity.objectMetadataId,
);
if (!isDefined(objectMetadataUniversalIdentifier)) {
throw new FlatEntityMapsException(
`ObjectMetadata with id ${objectPermissionEntity.objectMetadataId} not found for objectPermission ${objectPermissionEntity.id}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return {
...objectPermissionEntityWithoutRelations,
createdAt: objectPermissionEntity.createdAt.toISOString(),
updatedAt: objectPermissionEntity.updatedAt.toISOString(),
universalIdentifier:
objectPermissionEntityWithoutRelations.universalIdentifier,
applicationUniversalIdentifier,
roleUniversalIdentifier,
objectMetadataUniversalIdentifier,
};
};