Solo transaction application synchronization service refactor (#17864)
# Introduction Refactoring the application sync service to be making a single validate build and run transaction instead of calling all the services n times thanks to the universal workspace migration refactor that allow doing so ## What's next Migrating all below entities to by syncableEntities so they can be universalised too ( right now they're still calling the services n times and won't be returned in the workspace migration ) - [objectPermission](https://github.com/twentyhq/core-team-issues/issues/2223) - [fieldPermission](https://github.com/twentyhq/core-team-issues/issues/2224) - [permissionFlag](https://github.com/twentyhq/core-team-issues/issues/2225)
This commit is contained in:
+2
-14
@@ -4,20 +4,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { ApplicationResolver } from 'src/engine/core-modules/application/resolvers/application.resolver';
|
||||
import { MarketplaceResolver } from 'src/engine/core-modules/application/resolvers/marketplace.resolver';
|
||||
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/services/application-manifest-migration.service';
|
||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/services/application-sync.service';
|
||||
import { ApplicationVariableEntityModule } from 'src/engine/core-modules/applicationVariable/application-variable.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { FrontComponentModule } from 'src/engine/metadata-modules/front-component/front-component.module';
|
||||
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
||||
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
|
||||
@@ -30,14 +24,8 @@ import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workf
|
||||
TypeOrmModule.forFeature([FileEntity]),
|
||||
ApplicationModule,
|
||||
ApplicationVariableEntityModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
ObjectMetadataModule,
|
||||
FieldMetadataModule,
|
||||
DataSourceModule,
|
||||
LogicFunctionModule,
|
||||
WorkspaceMigrationModule,
|
||||
PermissionsModule,
|
||||
RoleModule,
|
||||
ObjectPermissionModule,
|
||||
PermissionFlagModule,
|
||||
WorkflowCommonModule,
|
||||
@@ -45,11 +33,11 @@ import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workf
|
||||
FileStorageModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
FrontComponentModule,
|
||||
],
|
||||
providers: [
|
||||
ApplicationResolver,
|
||||
MarketplaceResolver,
|
||||
ApplicationManifestMigrationService,
|
||||
ApplicationSyncService,
|
||||
WorkspaceMigrationGraphqlApiExceptionInterceptor,
|
||||
],
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
export const APPLICATION_MANIFEST_METADATA_NAMES = [
|
||||
'objectMetadata',
|
||||
'fieldMetadata',
|
||||
'logicFunction',
|
||||
'frontComponent',
|
||||
'role',
|
||||
] as const satisfies AllMetadataName[];
|
||||
|
||||
export type ApplicationManifestMetadataName =
|
||||
(typeof APPLICATION_MANIFEST_METADATA_NAMES)[number];
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type Manifest, type RoleManifest } from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { computeApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { getSubApplicationFromToAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-sub-application-from-to-all-flat-entity-maps.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationManifestMigrationService {
|
||||
private readonly logger = new Logger(
|
||||
ApplicationManifestMigrationService.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly objectPermissionService: ObjectPermissionService,
|
||||
private readonly fieldPermissionService: FieldPermissionService,
|
||||
private readonly permissionFlagService: PermissionFlagService,
|
||||
) {}
|
||||
|
||||
async syncMetadataFromManifest({
|
||||
manifest,
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
workspaceId: string;
|
||||
ownerFlatApplication: FlatApplication;
|
||||
}) {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const toAllUniversalFlatEntityMaps =
|
||||
computeApplicationManifestAllUniversalFlatEntityMaps({
|
||||
manifest,
|
||||
applicationUniversalIdentifier:
|
||||
ownerFlatApplication.universalIdentifier,
|
||||
now,
|
||||
});
|
||||
|
||||
const cacheResult = await this.workspaceCacheService.getOrRecompute(
|
||||
workspaceId,
|
||||
[
|
||||
...APPLICATION_MANIFEST_METADATA_NAMES.map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
),
|
||||
'featureFlagsMap',
|
||||
],
|
||||
);
|
||||
|
||||
const { featureFlagsMap, ...fromAllFlatEntityMaps } = cacheResult;
|
||||
|
||||
const fromToAllFlatEntityMaps = getSubApplicationFromToAllFlatEntityMaps({
|
||||
applicationId: ownerFlatApplication.id,
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigrationFromTo(
|
||||
{
|
||||
buildOptions: {
|
||||
isSystemBuild: true,
|
||||
inferDeletionFromMissingEntities: true,
|
||||
},
|
||||
fromToAllFlatEntityMaps,
|
||||
workspaceId,
|
||||
additionalCacheDataMaps: { featureFlagsMap },
|
||||
applicationUniversalIdentifier:
|
||||
ownerFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Validation errors occurred while syncing application manifest metadata',
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Metadata migration completed for application ${ownerFlatApplication.universalIdentifier}`,
|
||||
);
|
||||
|
||||
await this.syncRolePermissionsAndDefaultRole({
|
||||
manifest,
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated should be remove once below issues are resolved:
|
||||
* - [objectPermission](https://github.com/twentyhq/core-team-issues/issues/2223)
|
||||
* - [fieldPermission](https://github.com/twentyhq/core-team-issues/issues/2224)
|
||||
* - [permissionFlag](https://github.com/twentyhq/core-team-issues/issues/2225)
|
||||
*/
|
||||
private async syncRolePermissionsAndDefaultRole({
|
||||
manifest,
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
workspaceId: string;
|
||||
ownerFlatApplication: FlatApplication;
|
||||
}) {
|
||||
const {
|
||||
flatRoleMaps: refreshedFlatRoleMaps,
|
||||
flatObjectMetadataMaps: refreshedFlatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps: refreshedFlatFieldMetadataMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatRoleMaps',
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
]);
|
||||
|
||||
let defaultRoleId: string | null = null;
|
||||
|
||||
for (const role of manifest.roles) {
|
||||
const flatRole = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: refreshedFlatRoleMaps,
|
||||
universalIdentifier: role.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(flatRole)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to resolve role for universalIdentifier ${role.universalIdentifier}`,
|
||||
ApplicationExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.syncApplicationRolePermissions({
|
||||
role,
|
||||
workspaceId,
|
||||
roleId: flatRole.id,
|
||||
refreshedFlatObjectMetadataMaps,
|
||||
refreshedFlatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (
|
||||
role.universalIdentifier ===
|
||||
manifest.application.defaultRoleUniversalIdentifier
|
||||
) {
|
||||
defaultRoleId = flatRole.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(defaultRoleId)) {
|
||||
await this.applicationService.update(ownerFlatApplication.id, {
|
||||
defaultRoleId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async syncApplicationRolePermissions({
|
||||
role,
|
||||
workspaceId,
|
||||
roleId,
|
||||
refreshedFlatObjectMetadataMaps,
|
||||
refreshedFlatFieldMetadataMaps,
|
||||
}: {
|
||||
role: RoleManifest;
|
||||
workspaceId: string;
|
||||
roleId: string;
|
||||
refreshedFlatObjectMetadataMaps: Awaited<
|
||||
ReturnType<WorkspaceCacheService['getOrRecompute']>
|
||||
>['flatObjectMetadataMaps'];
|
||||
refreshedFlatFieldMetadataMaps: Awaited<
|
||||
ReturnType<WorkspaceCacheService['getOrRecompute']>
|
||||
>['flatFieldMetadataMaps'];
|
||||
}) {
|
||||
if (
|
||||
(role.objectPermissions ?? []).length > 0 ||
|
||||
(role.fieldPermissions ?? []).length > 0
|
||||
) {
|
||||
const formattedObjectPermissions = role.objectPermissions
|
||||
?.map((permission) => {
|
||||
const flatObjectMetadata = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: refreshedFlatObjectMetadataMaps,
|
||||
universalIdentifier: permission.objectUniversalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to find object with universalIdentifier ${permission.objectUniversalIdentifier}`,
|
||||
ApplicationExceptionCode.OBJECT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...permission,
|
||||
objectMetadataId: flatObjectMetadata.id,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
permission,
|
||||
): permission is typeof permission & { objectMetadataId: string } =>
|
||||
isDefined(permission.objectMetadataId),
|
||||
);
|
||||
|
||||
if (isDefined(formattedObjectPermissions)) {
|
||||
await this.objectPermissionService.upsertObjectPermissions({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId,
|
||||
objectPermissions: formattedObjectPermissions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const formattedFieldPermissions = role.fieldPermissions
|
||||
?.map((permission) => {
|
||||
const flatObjectMetadata = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: refreshedFlatObjectMetadataMaps,
|
||||
universalIdentifier: permission.objectUniversalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to find object with universalIdentifier ${permission.objectUniversalIdentifier}`,
|
||||
ApplicationExceptionCode.OBJECT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: refreshedFlatFieldMetadataMaps,
|
||||
universalIdentifier: permission.fieldUniversalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(flatFieldMetadata)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to find field with universalIdentifier ${permission.fieldUniversalIdentifier}`,
|
||||
ApplicationExceptionCode.FIELD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...permission,
|
||||
objectMetadataId: flatObjectMetadata.id,
|
||||
fieldMetadataId: flatFieldMetadata.id,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
permission,
|
||||
): permission is typeof permission & {
|
||||
objectMetadataId: string;
|
||||
fieldMetadataId: string;
|
||||
} =>
|
||||
isDefined(permission.objectMetadataId) &&
|
||||
isDefined(permission.fieldMetadataId),
|
||||
);
|
||||
|
||||
if (isDefined(formattedFieldPermissions)) {
|
||||
await this.fieldPermissionService.upsertFieldPermissions({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId,
|
||||
fieldPermissions: formattedFieldPermissions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(role.permissionFlags) && role.permissionFlags.length > 0) {
|
||||
await this.permissionFlagService.upsertPermissionFlags({
|
||||
workspaceId,
|
||||
input: {
|
||||
roleId,
|
||||
permissionFlagKeys: role.permissionFlags,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-1070
File diff suppressed because it is too large
Load Diff
+127
@@ -0,0 +1,127 @@
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
|
||||
import { type ApplicationManifestMetadataName } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/utils/from-field-manifest-to-universal-flat-field-metadata.util';
|
||||
import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/utils/from-front-component-manifest-to-universal-flat-front-component.util';
|
||||
import { fromLogicFunctionManifestToUniversalFlatLogicFunction } from 'src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util';
|
||||
import { fromObjectManifestToUniversalFlatObjectMetadata } from 'src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util';
|
||||
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/utils/from-role-manifest-to-universal-flat-role.util';
|
||||
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
|
||||
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
export type ApplicationManifestAllUniversalFlatEntityMaps = Pick<
|
||||
AllUniversalFlatEntityMaps,
|
||||
MetadataToFlatEntityMapsKey<ApplicationManifestMetadataName>
|
||||
>;
|
||||
|
||||
export type ApplicationManifestAllFlatEntityMaps = Pick<
|
||||
AllFlatEntityMaps,
|
||||
MetadataToFlatEntityMapsKey<ApplicationManifestMetadataName>
|
||||
>;
|
||||
|
||||
export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
|
||||
manifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): ApplicationManifestAllUniversalFlatEntityMaps => {
|
||||
const allUniversalFlatEntityMaps =
|
||||
getEmptyApplicationManifestAllUniversalFlatEntityMaps();
|
||||
|
||||
for (const objectManifest of manifest.objects) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'objectMetadata',
|
||||
universalFlatEntity: fromObjectManifestToUniversalFlatObjectMetadata({
|
||||
objectManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
|
||||
for (const fieldManifest of objectManifest.fields) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'fieldMetadata',
|
||||
universalFlatEntity: fromFieldManifestToUniversalFlatFieldMetadata({
|
||||
fieldManifest: {
|
||||
...fieldManifest,
|
||||
objectUniversalIdentifier: objectManifest.universalIdentifier,
|
||||
},
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const fieldManifest of manifest.fields) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'fieldMetadata',
|
||||
universalFlatEntity: fromFieldManifestToUniversalFlatFieldMetadata({
|
||||
fieldManifest: fieldManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const logicFunctionManifest of manifest.logicFunctions) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'logicFunction',
|
||||
universalFlatEntity:
|
||||
fromLogicFunctionManifestToUniversalFlatLogicFunction({
|
||||
logicFunctionManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const frontComponentManifest of manifest.frontComponents) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'frontComponent',
|
||||
universalFlatEntity:
|
||||
fromFrontComponentManifestToUniversalFlatFrontComponent({
|
||||
frontComponentManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const roleManifest of manifest.roles) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'role',
|
||||
universalFlatEntity: fromRoleManifestToUniversalFlatRole({
|
||||
roleManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return allUniversalFlatEntityMaps;
|
||||
};
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
type FieldManifest,
|
||||
type RelationFieldManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value';
|
||||
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
|
||||
const isRelationFieldManifest = (
|
||||
fieldManifest: FieldManifest<FieldMetadataType>,
|
||||
): fieldManifest is RelationFieldManifest =>
|
||||
isMorphOrRelationFieldMetadataType(fieldManifest.type);
|
||||
|
||||
const getRelationTargetUniversalIdentifiers = (
|
||||
fieldManifest: FieldManifest<FieldMetadataType>,
|
||||
): {
|
||||
relationTargetFieldMetadataUniversalIdentifier: string | null;
|
||||
relationTargetObjectMetadataUniversalIdentifier: string | null;
|
||||
} => {
|
||||
if (!isRelationFieldManifest(fieldManifest)) {
|
||||
return {
|
||||
relationTargetFieldMetadataUniversalIdentifier: null,
|
||||
relationTargetObjectMetadataUniversalIdentifier: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!fieldManifest.relationTargetFieldMetadataUniversalIdentifier ||
|
||||
!fieldManifest.relationTargetObjectMetadataUniversalIdentifier
|
||||
) {
|
||||
throw new ApplicationException(
|
||||
`Field "${fieldManifest.name}" is of type ${fieldManifest.type} but is missing relationTargetFieldMetadataUniversalIdentifier or relationTargetObjectMetadataUniversalIdentifier`,
|
||||
ApplicationExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
fieldManifest.relationTargetFieldMetadataUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
fieldManifest.relationTargetObjectMetadataUniversalIdentifier,
|
||||
};
|
||||
};
|
||||
|
||||
export const fromFieldManifestToUniversalFlatFieldMetadata = ({
|
||||
fieldManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
fieldManifest: FieldManifest<FieldMetadataType> & {
|
||||
objectUniversalIdentifier: string;
|
||||
};
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatFieldMetadata => {
|
||||
const {
|
||||
relationTargetFieldMetadataUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier,
|
||||
} = getRelationTargetUniversalIdentifiers(fieldManifest);
|
||||
|
||||
return {
|
||||
universalIdentifier: fieldManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
type: fieldManifest.type,
|
||||
name: fieldManifest.name,
|
||||
label: fieldManifest.label,
|
||||
description: fieldManifest.description ?? null,
|
||||
icon: fieldManifest.icon ?? null,
|
||||
standardOverrides: null,
|
||||
options: fieldManifest.options ?? null,
|
||||
defaultValue:
|
||||
fieldManifest.defaultValue ?? generateDefaultValue(fieldManifest.type),
|
||||
universalSettings: fieldManifest.universalSettings ?? null,
|
||||
isCustom: true,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isUIReadOnly: false,
|
||||
isNullable: fieldManifest.isNullable ?? true,
|
||||
isUnique: false,
|
||||
isLabelSyncedWithName: false,
|
||||
morphId: null,
|
||||
objectMetadataUniversalIdentifier: fieldManifest.objectUniversalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
viewFilterUniversalIdentifiers: [],
|
||||
kanbanAggregateOperationViewUniversalIdentifiers: [],
|
||||
calendarViewUniversalIdentifiers: [],
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { type FrontComponentManifest } from 'twenty-shared/application';
|
||||
|
||||
import { type UniversalFlatFrontComponent } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-front-component.type';
|
||||
|
||||
export const fromFrontComponentManifestToUniversalFlatFrontComponent = ({
|
||||
frontComponentManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
frontComponentManifest: FrontComponentManifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatFrontComponent => {
|
||||
return {
|
||||
universalIdentifier: frontComponentManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
name: frontComponentManifest.name ?? frontComponentManifest.componentName,
|
||||
description: frontComponentManifest.description ?? null,
|
||||
sourceComponentPath: frontComponentManifest.sourceComponentPath,
|
||||
builtComponentPath: frontComponentManifest.builtComponentPath,
|
||||
componentName: frontComponentManifest.componentName,
|
||||
builtComponentChecksum: frontComponentManifest.builtComponentChecksum,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { parse } from 'path';
|
||||
|
||||
import { type LogicFunctionManifest } from 'twenty-shared/application';
|
||||
|
||||
import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { type UniversalFlatLogicFunction } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type';
|
||||
|
||||
export const fromLogicFunctionManifestToUniversalFlatLogicFunction = ({
|
||||
logicFunctionManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
logicFunctionManifest: LogicFunctionManifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatLogicFunction => {
|
||||
const name =
|
||||
logicFunctionManifest.name ?? parse(logicFunctionManifest.handlerName).name;
|
||||
|
||||
return {
|
||||
universalIdentifier: logicFunctionManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
name,
|
||||
description: logicFunctionManifest.description ?? null,
|
||||
runtime: LogicFunctionRuntime.NODE22,
|
||||
timeoutSeconds: logicFunctionManifest.timeoutSeconds ?? 300,
|
||||
sourceHandlerPath: logicFunctionManifest.sourceHandlerPath,
|
||||
builtHandlerPath: logicFunctionManifest.builtHandlerPath,
|
||||
handlerName: logicFunctionManifest.handlerName,
|
||||
checksum: logicFunctionManifest.builtHandlerChecksum,
|
||||
toolInputSchema: logicFunctionManifest.toolInputSchema ?? null,
|
||||
isTool: logicFunctionManifest.isTool ?? false,
|
||||
cronTriggerSettings: logicFunctionManifest.cronTriggerSettings ?? null,
|
||||
databaseEventTriggerSettings:
|
||||
logicFunctionManifest.databaseEventTriggerSettings ?? null,
|
||||
httpRouteTriggerSettings:
|
||||
logicFunctionManifest.httpRouteTriggerSettings ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
};
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { type ObjectManifest } from 'twenty-shared/application';
|
||||
|
||||
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
|
||||
|
||||
export const fromObjectManifestToUniversalFlatObjectMetadata = ({
|
||||
objectManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
objectManifest: ObjectManifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatObjectMetadata => {
|
||||
return {
|
||||
universalIdentifier: objectManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
nameSingular: objectManifest.nameSingular,
|
||||
namePlural: objectManifest.namePlural,
|
||||
labelSingular: objectManifest.labelSingular,
|
||||
labelPlural: objectManifest.labelPlural,
|
||||
description: objectManifest.description ?? null,
|
||||
icon: objectManifest.icon ?? null,
|
||||
standardOverrides: null,
|
||||
targetTableName: 'DEPRECATED',
|
||||
isCustom: true,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isUIReadOnly: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: false,
|
||||
duplicateCriteria: null,
|
||||
shortcut: null,
|
||||
isLabelSyncedWithName: true,
|
||||
fieldUniversalIdentifiers: [],
|
||||
indexMetadataUniversalIdentifiers: [],
|
||||
viewUniversalIdentifiers: [],
|
||||
labelIdentifierFieldMetadataUniversalIdentifier: null,
|
||||
imageIdentifierFieldMetadataUniversalIdentifier: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { type RoleManifest } from 'twenty-shared/application';
|
||||
|
||||
import { type UniversalFlatRole } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role.type';
|
||||
|
||||
export const fromRoleManifestToUniversalFlatRole = ({
|
||||
roleManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
roleManifest: RoleManifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): UniversalFlatRole => {
|
||||
return {
|
||||
universalIdentifier: roleManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
label: roleManifest.label,
|
||||
description: roleManifest.description ?? null,
|
||||
icon: roleManifest.icon ?? null,
|
||||
canUpdateAllSettings: roleManifest.canUpdateAllSettings ?? false,
|
||||
canAccessAllTools: roleManifest.canAccessAllTools ?? false,
|
||||
canReadAllObjectRecords: roleManifest.canReadAllObjectRecords ?? false,
|
||||
canUpdateAllObjectRecords: roleManifest.canUpdateAllObjectRecords ?? false,
|
||||
canSoftDeleteAllObjectRecords:
|
||||
roleManifest.canSoftDeleteAllObjectRecords ?? false,
|
||||
canDestroyAllObjectRecords:
|
||||
roleManifest.canDestroyAllObjectRecords ?? false,
|
||||
isEditable: true,
|
||||
canBeAssignedToUsers: roleManifest.canBeAssignedToUsers ?? true,
|
||||
canBeAssignedToAgents: roleManifest.canBeAssignedToAgents ?? true,
|
||||
canBeAssignedToApiKeys: roleManifest.canBeAssignedToApiKeys ?? true,
|
||||
roleTargetUniversalIdentifiers: [],
|
||||
rowLevelPermissionPredicateUniversalIdentifiers: [],
|
||||
rowLevelPermissionPredicateGroupUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { type ApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
|
||||
export const getEmptyApplicationManifestAllUniversalFlatEntityMaps =
|
||||
(): ApplicationManifestAllUniversalFlatEntityMaps => {
|
||||
const result = {} as ApplicationManifestAllUniversalFlatEntityMaps;
|
||||
|
||||
for (const metadataName of APPLICATION_MANIFEST_METADATA_NAMES) {
|
||||
const key = getMetadataFlatEntityMapsKey(metadataName);
|
||||
|
||||
result[key] = { byUniversalIdentifier: {} };
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import {
|
||||
type ApplicationManifestAllFlatEntityMaps,
|
||||
type ApplicationManifestAllUniversalFlatEntityMaps,
|
||||
} from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getSubFlatEntityMapsByApplicationIdOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-by-application-id-or-throw.util';
|
||||
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
|
||||
export type FromToApplicationManifestAllUniversalFlatEntityMaps = {
|
||||
[P in keyof ApplicationManifestAllFlatEntityMaps]?: FromTo<
|
||||
AllUniversalFlatEntityMaps[P]
|
||||
>;
|
||||
};
|
||||
|
||||
export const getSubApplicationFromToAllFlatEntityMaps = ({
|
||||
applicationId,
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps,
|
||||
}: {
|
||||
applicationId: string;
|
||||
fromAllFlatEntityMaps: ApplicationManifestAllFlatEntityMaps;
|
||||
toAllUniversalFlatEntityMaps: ApplicationManifestAllUniversalFlatEntityMaps;
|
||||
}): FromToApplicationManifestAllUniversalFlatEntityMaps => {
|
||||
const fromToAllFlatEntityMaps: FromToApplicationManifestAllUniversalFlatEntityMaps =
|
||||
{};
|
||||
|
||||
for (const metadataName of APPLICATION_MANIFEST_METADATA_NAMES) {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
|
||||
const fromFlatEntityMaps = fromAllFlatEntityMaps[flatEntityMapsKey];
|
||||
const toFlatEntityMaps = toAllUniversalFlatEntityMaps[flatEntityMapsKey];
|
||||
|
||||
const fromTo = {
|
||||
from: getSubFlatEntityMapsByApplicationIdOrThrow<
|
||||
MetadataFlatEntity<typeof metadataName>
|
||||
>({
|
||||
applicationId,
|
||||
flatEntityMaps: fromFlatEntityMaps,
|
||||
}),
|
||||
to: toFlatEntityMaps,
|
||||
};
|
||||
|
||||
// @ts-expect-error Metadata flat entity maps cache key and metadataName colliding
|
||||
fromToAllFlatEntityMaps[flatEntityMapsKey] = fromTo;
|
||||
}
|
||||
|
||||
return fromToAllFlatEntityMaps;
|
||||
};
|
||||
Reference in New Issue
Block a user