From cceeb6ed4d77dd3d0bacbb01f320f85f6cde8d06 Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 17 Oct 2025 19:23:00 +0200 Subject: [PATCH] Add applicationId to syncableEntity and fix syncApp deletion (#15170) ## Context - All flatEntity should extend SyncableEntity - SyncableEntity should now have applicationId and application relation - Fix syncApp deletion, should now properly use migration v2 to delete syncable entities --- ...tions-install-delete-reinstall.e2e-spec.ts | 26 +-- ...1795-addApplicationIdToSyncableEntities.ts | 217 ++++++++++++++++++ .../application/application-sync.service.ts | 114 ++++++++- .../application/application.entity.ts | 24 +- .../application/application.module.ts | 4 +- .../application/application.resolver.ts | 26 +-- .../application-variable.service.ts | 5 + .../metadata-modules/agent/agent.entity.ts | 21 +- .../metadata-modules/agent/dtos/agent.dto.ts | 2 +- .../dtos/create-cron-trigger.input.ts | 3 + .../entities/cron-trigger.entity.ts | 5 +- ...ace-flat-cron-trigger-map-cache.service.ts | 26 +-- .../types/flat-cron-trigger.type.ts | 7 +- ...trigger-input-to-flat-cron-trigger.util.ts | 1 + .../create-database-event-trigger.input.ts | 3 + ...atabase-event-trigger-map-cache.service.ts | 41 ++-- .../types/flat-database-event-trigger.type.ts | 7 +- ...put-to-flat-database-event-trigger.util.ts | 1 + .../field-metadata/field-metadata.entity.ts | 8 +- .../flat-agent/types/flat-agent.type.ts | 7 +- .../empty-flat-entity-maps.constant.ts | 1 + .../types/flat-entity-maps.type.ts | 1 + .../flat-entity/types/flat-entity.type.ts | 22 +- ...ntity-to-flat-entity-maps-or-throw.util.ts | 17 +- ...ity-from-flat-entity-maps-or-throw.util.ts | 27 +++ ...et-flat-entities-by-application-id.util.ts | 38 +++ .../__mocks__/get-flat-field-metadata.mock.ts | 2 +- ...elation-target-flat-field-metadata-mock.ts | 1 + ...lat-field-metadatas-to-create.spec.ts.snap | 6 + .../types/flat-field-metadata.type.ts | 1 + ...rate-index-for-flat-field-metadata.util.ts | 1 + ...d-metadata-from-create-field-input.util.ts | 1 + ...-field-metadata-is-nullable-update.util.ts | 1 + ...flat-field-metadata-options-update.util.ts | 1 + .../__mocks__/get-flat-index-metadata.mock.ts | 1 + .../types/flat-index-metadata.type.ts | 4 +- ...-flat-object-metadata-map-cache.service.ts | 30 +-- .../types/flat-object-metadata.type.ts | 4 +- ...and-flat-field-metadatas-to-create.util.ts | 1 + ...fter-flat-object-identifier-update.util.ts | 1 + .../flat-role/types/flat-role.type.ts | 7 +- .../from-role-entity-to-flat-role.util.ts | 1 + .../types/flat-view-field.type.ts | 7 +- ...input-to-flat-view-field-to-create.util.ts | 4 +- .../types/flat-view-filter.type.ts | 7 +- ...nput-to-flat-view-filter-to-create.util.ts | 4 +- .../types/flat-view-group.type.ts | 7 +- ...input-to-flat-view-group-to-create.util.ts | 4 +- .../flat-view/types/flat-view.type.ts | 7 +- ...-view-input-to-flat-view-to-create.util.ts | 6 +- .../object-metadata/object-metadata.entity.ts | 19 +- ...-field-metadatas-for-custom-object.util.ts | 12 +- ...ld-default-index-for-custom-object.util.ts | 1 + ...-field-metadatas-for-custom-object.util.ts | 2 + .../metadata-modules/role/role.entity.ts | 4 +- .../dtos/create-route-trigger.input.ts | 3 + ...ce-flat-route-trigger-map-cache.service.ts | 26 +-- .../types/flat-route-trigger.type.ts | 7 +- ...rigger-input-to-flat-route-trigger.util.ts | 1 + .../serverless-function-layer.entity.ts | 11 - .../serverless-function.entity.ts | 17 +- ...t-serverless-function-map-cache.service.ts | 41 ++-- .../types/flat-serverless-function.type.ts | 10 +- .../dtos/inputs/create-view-field.input.ts | 8 +- .../dtos/inputs/create-view-filter.input.ts | 12 +- .../dtos/inputs/create-view-group.input.ts | 8 +- .../view-group/entities/view-group.entity.ts | 5 +- .../view/dtos/inputs/create-view.input.ts | 8 +- ...ce-migration-build-orchestrator.service.ts | 8 + .../partial-index-metadata.interface.ts | 2 + .../standard-roles/roles/admin-role.ts | 1 + .../roles/data-manipulator-role.ts | 1 + .../roles/data-navigator-role.ts | 1 + .../roles/workflow-manager-role.ts | 1 + .../interfaces/syncable-entity.interface.ts | 14 +- .../src/types/non-nullable-properties.type.ts | 3 + .../get-field-metadata-entity.mock.ts | 4 + .../get-object-metadata-entity.mock.ts | 6 +- .../agent/utils/agent-tool-test-utils.ts | 6 +- 79 files changed, 734 insertions(+), 271 deletions(-) create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1760700501795-addApplicationIdToSyncableEntities.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-flat-entities-by-application-id.util.ts create mode 100644 packages/twenty-server/src/types/non-nullable-properties.type.ts diff --git a/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts b/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts index 94d425efa28..5718818fbfa 100644 --- a/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts +++ b/packages/twenty-cli/src/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts @@ -15,15 +15,14 @@ describe.each(COVERED_APPLICATION_FOLDERS)( expect(existsSync(appPath)).toBe(true); }); - // TODO uncomment when reinstall is fix to cleanup gracefully - // afterAll(async () => { - // const result = await deleteCommand.execute({ - // appPath, - // askForConfirmation: false, - // }); + afterAll(async () => { + const result = await deleteCommand.execute({ + appPath, + askForConfirmation: false, + }); - // expect(result.success).toBe(true); - // }); + expect(result.success).toBe(true); + }); it(`should successfully install ${applicationName} application`, async () => { const result = await syncCommand.execute(appPath); @@ -40,13 +39,10 @@ describe.each(COVERED_APPLICATION_FOLDERS)( expect(result.success).toBe(true); }); - it.failing( - `should successfully re-install ${applicationName} application`, - async () => { - const result = await syncCommand.execute(appPath); + it(`should successfully re-install ${applicationName} application`, async () => { + const result = await syncCommand.execute(appPath); - expect(result.success).toBe(true); - }, - ); + expect(result.success).toBe(true); + }); }, ); diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1760700501795-addApplicationIdToSyncableEntities.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1760700501795-addApplicationIdToSyncableEntities.ts new file mode 100644 index 00000000000..ac2b46dad54 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1760700501795-addApplicationIdToSyncableEntities.ts @@ -0,0 +1,217 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddApplicationIdToSyncableEntities1760700501795 + implements MigrationInterface +{ + name = 'AddApplicationIdToSyncableEntities1760700501795'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."application" DROP CONSTRAINT "FK_eec488855d08b312a869a13ccb1"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."agent" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."role" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."role" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilter" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewGroup" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."objectMetadata" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."indexMetadata" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" ADD "applicationId" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."application" DROP CONSTRAINT "UQ_eec488855d08b312a869a13ccb1"`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_0cc4d03dbcc269e77ba4d297fb" ON "core"."agent" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_3b7ff27925c0959777682c1adc" ON "core"."role" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_3a00d35710f4227ded320fd96d" ON "core"."objectMetadata" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_f1c88fdfc3ad8910b17fc1fd73" ON "core"."fieldMetadata" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."role" ADD CONSTRAINT "FK_7f3b96f15aaf5a27549288d264b" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilter" ADD CONSTRAINT "FK_d5651cf33fa56a47cd262a3fb2c" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ADD CONSTRAINT "FK_bfc3498b964ef1bfc89b1f2bee3" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewGroup" ADD CONSTRAINT "FK_5aff384532c78fa8a42ceeae282" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ADD CONSTRAINT "FK_ff8cbebe1704954120df82bf393" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ADD CONSTRAINT "FK_348e25d584c7e51417f4e097941" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."indexMetadata" ADD CONSTRAINT "FK_056363e1599f5b9a0e33323d9da" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" ADD CONSTRAINT "FK_05453a954e458e3d91f2ff5043f" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_b560ea62a958deff0c6059caa45" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" ADD CONSTRAINT "FK_817ea28e71e3b19acc258dd7dcd" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" ADD CONSTRAINT "FK_9acc2804037a5c885633024368d" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" ADD CONSTRAINT "FK_6edf47a8bfe17a5811998dc7162" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" DROP CONSTRAINT "FK_6edf47a8bfe17a5811998dc7162"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" DROP CONSTRAINT "FK_9acc2804037a5c885633024368d"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" DROP CONSTRAINT "FK_817ea28e71e3b19acc258dd7dcd"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_b560ea62a958deff0c6059caa45"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" DROP CONSTRAINT "FK_05453a954e458e3d91f2ff5043f"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."indexMetadata" DROP CONSTRAINT "FK_056363e1599f5b9a0e33323d9da"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" DROP CONSTRAINT "FK_348e25d584c7e51417f4e097941"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" DROP CONSTRAINT "FK_ff8cbebe1704954120df82bf393"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewGroup" DROP CONSTRAINT "FK_5aff384532c78fa8a42ceeae282"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" DROP CONSTRAINT "FK_bfc3498b964ef1bfc89b1f2bee3"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilter" DROP CONSTRAINT "FK_d5651cf33fa56a47cd262a3fb2c"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."role" DROP CONSTRAINT "FK_7f3b96f15aaf5a27549288d264b"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_f1c88fdfc3ad8910b17fc1fd73"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_3a00d35710f4227ded320fd96d"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_3b7ff27925c0959777682c1adc"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_0cc4d03dbcc269e77ba4d297fb"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."application" ADD CONSTRAINT "UQ_eec488855d08b312a869a13ccb1" UNIQUE ("serverlessFunctionLayerId")`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."indexMetadata" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."objectMetadata" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewGroup" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilter" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."role" DROP COLUMN "applicationId"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."role" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."agent" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."application" ADD CONSTRAINT "FK_eec488855d08b312a869a13ccb1" FOREIGN KEY ("serverlessFunctionLayerId") REFERENCES "core"."serverlessFunctionLayer"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-sync.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-sync.service.ts index bcfe52ad980..29155652a7d 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-sync.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-sync.service.ts @@ -15,20 +15,26 @@ import { ServerlessFunctionManifest, ServerlessFunctionTriggerManifest, } from 'src/engine/core-modules/application/types/application.types'; +import { ApplicationVariableService } from 'src/engine/core-modules/applicationVariable/application-variable.service'; import { AgentService } from 'src/engine/metadata-modules/agent/agent.service'; import { CronTriggerV2Service } from 'src/engine/metadata-modules/cron-trigger/services/cron-trigger-v2.service'; import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DatabaseEventTriggerV2Service } from 'src/engine/metadata-modules/database-event-trigger/services/database-event-trigger-v2.service'; import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type'; +import { ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant'; +import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type'; +import { getFlatEntitiesByApplicationId } from 'src/engine/metadata-modules/flat-entity/utils/get-flat-entities-by-application-id.util'; +import { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util'; import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service'; import { RouteTriggerV2Service } from 'src/engine/metadata-modules/route-trigger/services/route-trigger-v2.service'; import { FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type'; import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service'; import { ServerlessFunctionV2Service } from 'src/engine/metadata-modules/serverless-function/services/serverless-function-v2.service'; import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type'; -import { ApplicationVariableService } from 'src/engine/core-modules/applicationVariable/application-variable.service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class ApplicationSyncService { @@ -46,6 +52,7 @@ export class ApplicationSyncService { private readonly databaseEventTriggerV2Service: DatabaseEventTriggerV2Service, private readonly cronTriggerV2Service: CronTriggerV2Service, private readonly routeTriggerV2Service: RouteTriggerV2Service, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, ) {} public async synchronizeFromManifest({ @@ -795,4 +802,109 @@ export class ApplicationSyncService { ); } } + + public async deleteApplication({ + workspaceId, + applicationUniversalIdentifier, + }: { + workspaceId: string; + applicationUniversalIdentifier: string; + }) { + const { + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + flatIndexMaps: existingFlatIndexMetadataMaps, + flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatObjectMetadataMaps', + 'flatIndexMaps', + 'flatFieldMetadataMaps', + ], + }, + ); + + const application = await this.applicationService.findByUniversalIdentifier( + applicationUniversalIdentifier, + workspaceId, + ); + + if (!isDefined(application)) { + throw new ApplicationException( + `Application with universalIdentifier ${applicationUniversalIdentifier} not found`, + ApplicationExceptionCode.ENTITY_NOT_FOUND, + ); + } + + const flatObjectMetadataMapsByApplicationId = + getFlatEntitiesByApplicationId( + existingFlatObjectMetadataMaps, + application.id, + ); + + const fromFlatObjectMetadataMaps = getSubFlatEntityMapsOrThrow({ + flatEntityIds: flatObjectMetadataMapsByApplicationId.map((obj) => obj.id), + flatEntityMaps: existingFlatObjectMetadataMaps, + }); + + const flatIndexMetadataMapsByApplicationId = getFlatEntitiesByApplicationId( + existingFlatIndexMetadataMaps, + application.id, + ); + + const fromFlatIndexMetadataMaps = getSubFlatEntityMapsOrThrow({ + flatEntityIds: flatIndexMetadataMapsByApplicationId.map( + (field) => field.id, + ), + flatEntityMaps: existingFlatIndexMetadataMaps, + }); + + const flatFieldMetadataMapsByApplicationId = getFlatEntitiesByApplicationId( + existingFlatFieldMetadataMaps, + application.id, + ); + + const fromFlatFieldMetadataMaps = getSubFlatEntityMapsOrThrow({ + flatEntityIds: flatFieldMetadataMapsByApplicationId.map((idx) => idx.id), + flatEntityMaps: existingFlatFieldMetadataMaps, + }); + + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatObjectMetadataMaps: { + from: fromFlatObjectMetadataMaps, + to: EMPTY_FLAT_ENTITY_MAPS, + }, + flatIndexMaps: { + from: fromFlatIndexMetadataMaps, + to: EMPTY_FLAT_ENTITY_MAPS, + }, + flatFieldMetadataMaps: { + from: fromFlatFieldMetadataMaps, + to: EMPTY_FLAT_ENTITY_MAPS, + }, + }, + workspaceId, + buildOptions: { + isSystemBuild: true, + inferDeletionFromMissingEntities: { + ...Object.values(ALL_METADATA_NAME).reduce( + (acc, metadataName) => ({ + ...acc, + [metadataName]: true, + }), + {} as Partial>, + ), + }, + }, + }, + ); + + await this.applicationService.delete( + applicationUniversalIdentifier, + workspaceId, + ); + } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application.entity.ts b/packages/twenty-server/src/engine/core-modules/application/application.entity.ts index 5e13f924770..da49bcd2692 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application.entity.ts @@ -7,18 +7,16 @@ import { JoinColumn, ManyToOne, OneToMany, - OneToOne, PrimaryGeneratedColumn, Relation, UpdateDateColumn, } from 'typeorm'; +import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity'; import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity'; @Entity({ name: 'application', schema: 'core' }) @Index('IDX_APPLICATION_WORKSPACE_ID', ['workspaceId']) @@ -58,15 +56,11 @@ export class ApplicationEntity { @Column({ nullable: false, type: 'uuid' }) serverlessFunctionLayerId: string; - @OneToOne( - () => ServerlessFunctionLayerEntity, - (serverlessFunctionLayer) => serverlessFunctionLayer.application, - { - onDelete: 'CASCADE', - }, - ) - @JoinColumn({ name: 'serverlessFunctionLayerId' }) - serverlessFunctionLayer: Relation; + @ManyToOne(() => Workspace, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Relation; @OneToMany(() => AgentEntity, (agent) => agent.application, { onDelete: 'CASCADE', @@ -96,12 +90,6 @@ export class ApplicationEntity { ) applicationVariables: Relation; - @ManyToOne(() => Workspace, { - onDelete: 'CASCADE', - }) - @JoinColumn({ name: 'workspaceId' }) - workspace: Relation; - @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; diff --git a/packages/twenty-server/src/engine/core-modules/application/application.module.ts b/packages/twenty-server/src/engine/core-modules/application/application.module.ts index d12ffb3b0fa..125a14c5e24 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application.module.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application.module.ts @@ -5,6 +5,7 @@ import { ApplicationSyncService } from 'src/engine/core-modules/application/appl import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ApplicationResolver } from 'src/engine/core-modules/application/application.resolver'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { ApplicationVariableModule } from 'src/engine/core-modules/applicationVariable/application-variable.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity'; import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module'; @@ -16,7 +17,7 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat import { RouteTriggerModule } from 'src/engine/metadata-modules/route-trigger/route-trigger.module'; import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module'; import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; -import { ApplicationVariableModule } from 'src/engine/core-modules/applicationVariable/application-variable.module'; +import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module'; @Module({ imports: [ @@ -31,6 +32,7 @@ import { ApplicationVariableModule } from 'src/engine/core-modules/applicationVa DatabaseEventTriggerModule, CronTriggerModule, RouteTriggerModule, + WorkspaceMigrationV2Module, ], providers: [ApplicationResolver, ApplicationService, ApplicationSyncService], }) diff --git a/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts index 681653db48b..a3b614c1ac4 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application.resolver.ts @@ -1,18 +1,18 @@ import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -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 { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service'; -import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input'; -import { DeleteApplicationInput } from 'src/engine/core-modules/application/dtos/deleteApplication.input'; -import { ApplicationService } from 'src/engine/core-modules/application/application.service'; -import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter'; +import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto'; +import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input'; +import { DeleteApplicationInput } from 'src/engine/core-modules/application/dtos/deleteApplication.input'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { RequireFeatureFlag } from 'src/engine/guards/feature-flag.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; @UseGuards(WorkspaceAuthGuard) @Resolver() @@ -58,10 +58,10 @@ export class ApplicationResolver { @Args() { packageJson }: DeleteApplicationInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - await this.applicationService.delete( - packageJson.universalIdentifier, + await this.applicationSyncService.deleteApplication({ + applicationUniversalIdentifier: packageJson.universalIdentifier, workspaceId, - ); + }); return true; } diff --git a/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.service.ts b/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.service.ts index 1db1c6c3430..9111e1c5f22 100644 --- a/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.service.ts +++ b/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.service.ts @@ -1,5 +1,6 @@ import { InjectRepository } from '@nestjs/typeorm'; +import { isDefined } from 'twenty-shared/utils'; import { In, Not, Repository } from 'typeorm'; import { ApplicationVariable } from 'src/engine/core-modules/applicationVariable/application-variable.entity'; @@ -37,6 +38,10 @@ export class ApplicationVariableService { >; applicationId: string; }) { + if (!isDefined(env)) { + return; + } + for (const [key, { value, description, isSecret }] of Object.entries(env)) { await this.applicationVariableRepository.upsert( { diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent.entity.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent.entity.ts index 343858f0034..039edacabf8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/agent.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent.entity.ts @@ -12,9 +12,9 @@ import { } from 'typeorm'; import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const'; -import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ModelConfiguration } from 'src/engine/metadata-modules/agent/types/modelConfiguration'; @@ -27,12 +27,15 @@ import { AgentHandoffEntity } from './agent-handoff.entity'; unique: true, where: '"deletedAt" IS NULL', }) -export class AgentEntity { +export class AgentEntity + extends SyncableEntity + implements Required +{ @PrimaryGeneratedColumn('uuid') id: string; @Column({ nullable: true, type: 'uuid' }) - standardId?: string; + standardId: string | null; @Column({ nullable: false }) name: string; @@ -58,9 +61,6 @@ export class AgentEntity { @Column({ nullable: false, type: 'uuid' }) workspaceId: string; - @Column({ nullable: true, type: 'uuid' }) - applicationId: string | null; - @Column({ default: false }) isCustom: boolean; @@ -70,13 +70,6 @@ export class AgentEntity { @JoinColumn({ name: 'workspaceId' }) workspace: Relation; - @ManyToOne(() => ApplicationEntity, (application) => application.agents, { - onDelete: 'CASCADE', - nullable: true, - }) - @JoinColumn({ name: 'applicationId' }) - application: Relation | null; - @OneToMany(() => AgentChatThreadEntity, (chatThread) => chatThread.agent) chatThreads: Relation; @@ -93,7 +86,7 @@ export class AgentEntity { updatedAt: Date; @DeleteDateColumn({ type: 'timestamptz' }) - deletedAt?: Date; + deletedAt: Date | null; @Column({ nullable: true, type: 'jsonb' }) modelConfiguration: ModelConfiguration; diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/dtos/agent.dto.ts b/packages/twenty-server/src/engine/metadata-modules/agent/dtos/agent.dto.ts index e8ef123680d..1fd544f0378 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/dtos/agent.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/dtos/agent.dto.ts @@ -21,7 +21,7 @@ export class AgentDTO { id: string; @Field(() => UUIDScalarType, { nullable: true }) - standardId?: string; + standardId: string | null; @IsString() @Field() diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input.ts index 2740f7937af..d3c634c8a99 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input.ts @@ -19,4 +19,7 @@ export class CreateCronTriggerInput { @HideField() universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity.ts index 0bb2eb81c1c..75e0e26fa26 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity.ts @@ -25,7 +25,10 @@ export const CRON_TRIGGER_ENTITY_RELATION_PROPERTIES = [ @Entity({ name: 'cronTrigger', schema: 'core' }) @Index('IDX_CRON_TRIGGER_WORKSPACE_ID', ['workspaceId']) -export class CronTrigger extends SyncableEntity { +export class CronTrigger + extends SyncableEntity + implements Required +{ @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts index 456b1907306..41245cba789 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts @@ -14,6 +14,7 @@ import { import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type'; import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; @@ -42,28 +43,19 @@ export class WorkspaceFlatCronTriggerMapCacheService extends WorkspaceFlatMapCac }, }); - const flatCronTriggerMaps = cronTriggers.reduce< - FlatEntityMaps - >((flatEntityMaps, cronTrigger) => { + return cronTriggers.reduce((flatCronTriggerMaps, cronTriggerEntity) => { const flatCronTrigger = { - ...removePropertiesFromRecord(cronTrigger, [ + ...removePropertiesFromRecord(cronTriggerEntity, [ ...CRON_TRIGGER_ENTITY_RELATION_PROPERTIES, ]), - universalIdentifier: cronTrigger.universalIdentifier ?? cronTrigger.id, + universalIdentifier: + cronTriggerEntity.universalIdentifier ?? cronTriggerEntity.id, } satisfies FlatCronTrigger; - return { - byId: { - ...flatEntityMaps.byId, - [flatCronTrigger.id]: flatCronTrigger, - }, - idByUniversalIdentifier: { - ...flatEntityMaps.idByUniversalIdentifier, - [flatCronTrigger.universalIdentifier]: flatCronTrigger.id, - }, - }; + return addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatCronTrigger, + flatEntityMaps: flatCronTriggerMaps, + }); }, EMPTY_FLAT_ENTITY_MAPS); - - return flatCronTriggerMaps; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type.ts index d5c7c71694c..d5332504b8a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type.ts @@ -1,5 +1,6 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; @@ -9,9 +10,7 @@ export type CronTriggerEntityRelationProperties = ServerlessFunctionEntity | Workspace >; -export type FlatCronTrigger = Omit< +export type FlatCronTrigger = FlatEntityFrom< CronTrigger, CronTriggerEntityRelationProperties -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util.ts index 0c3abb647ca..7af92a743f4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util.ts @@ -17,6 +17,7 @@ export const fromCreateCronTriggerInputToFlatCronTrigger = ({ universalIdentifier: createCronTriggerInput.universalIdentifier ?? v4(), settings: createCronTriggerInput.settings, serverlessFunctionId: createCronTriggerInput.serverlessFunctionId, + applicationId: createCronTriggerInput.applicationId ?? null, workspaceId, createdAt: now, updatedAt: now, diff --git a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input.ts b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input.ts index 2b2da7e94b4..29dc253c437 100644 --- a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input.ts @@ -19,4 +19,7 @@ export class CreateDatabaseEventTriggerInput { @HideField() universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts index bf3e8f81b77..a7990f35404 100644 --- a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts @@ -14,6 +14,7 @@ import { import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type'; import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; @@ -43,29 +44,23 @@ export class WorkspaceFlatDatabaseEventTriggerMapCacheService extends WorkspaceF }, }); - const flatDatabaseEventTriggerMaps = databaseEventTriggers.reduce< - FlatEntityMaps - >((flatEntityMaps, databaseEventTrigger) => { - const flatDatabaseEventTrigger = { - ...removePropertiesFromRecord(databaseEventTrigger, [ - ...DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES, - ]), - universalIdentifier: databaseEventTrigger.universalIdentifier ?? '', - } satisfies FlatDatabaseEventTrigger; + return databaseEventTriggers.reduce( + (flatDatabaseEventTriggerMaps, databaseEventTriggerEntity) => { + const flatDatabaseEventTrigger = { + ...removePropertiesFromRecord(databaseEventTriggerEntity, [ + ...DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES, + ]), + universalIdentifier: + databaseEventTriggerEntity.universalIdentifier ?? + databaseEventTriggerEntity.id, + } satisfies FlatDatabaseEventTrigger; - return { - byId: { - ...flatEntityMaps.byId, - [flatDatabaseEventTrigger.id]: flatDatabaseEventTrigger, - }, - idByUniversalIdentifier: { - ...flatEntityMaps.idByUniversalIdentifier, - [flatDatabaseEventTrigger.universalIdentifier]: - flatDatabaseEventTrigger.id, - }, - }; - }, EMPTY_FLAT_ENTITY_MAPS); - - return flatDatabaseEventTriggerMaps; + return addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatDatabaseEventTrigger, + flatEntityMaps: flatDatabaseEventTriggerMaps, + }); + }, + EMPTY_FLAT_ENTITY_MAPS, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type.ts b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type.ts index a2413cc5ec6..6f2a38a5c47 100644 --- a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type.ts @@ -1,5 +1,6 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; @@ -9,9 +10,7 @@ export type DatabaseEventTriggerEntityRelationProperties = ServerlessFunctionEntity | Workspace >; -export type FlatDatabaseEventTrigger = Omit< +export type FlatDatabaseEventTrigger = FlatEntityFrom< DatabaseEventTrigger, DatabaseEventTriggerEntityRelationProperties -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util.ts b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util.ts index 2bf45ed8b39..d42ae02e9d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util.ts @@ -21,5 +21,6 @@ export const fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger = ({ workspaceId, createdAt: now, updatedAt: now, + applicationId: createDatabaseEventTriggerInput.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts index 840b855150d..eff155956ac 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts @@ -18,6 +18,7 @@ import { import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; import { type FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto'; import { AssignIfIsGivenFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/assign-if-is-given-field-metadata-type.type'; @@ -49,10 +50,11 @@ import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities 'objectMetadataId', 'workspaceId', ]) -// TODO add some documentation about this entity export class FieldMetadataEntity< - TFieldMetadataType extends FieldMetadataType = FieldMetadataType, -> implements Required + TFieldMetadataType extends FieldMetadataType = FieldMetadataType, + > + extends SyncableEntity + implements Required { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts index 696153cfaaa..263de6bf2b6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts @@ -1,4 +1,5 @@ import { type AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; export const agentEntityRelationProperties = [ 'workspace', @@ -11,9 +12,7 @@ export const agentEntityRelationProperties = [ export type AgentEntityRelationProperties = (typeof agentEntityRelationProperties)[number]; -export type FlatAgent = Omit< +export type FlatAgent = FlatEntityFrom< AgentEntity, AgentEntityRelationProperties | 'createdAt' | 'updatedAt' | 'deletedAt' -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant.ts index b66a252070e..eaa7bf1bd55 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant.ts @@ -4,4 +4,5 @@ import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/f export const EMPTY_FLAT_ENTITY_MAPS = { byId: {}, idByUniversalIdentifier: {}, + universalIdentifiersByApplicationId: {}, } as const satisfies FlatEntityMaps; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type.ts index 717cd73e5c4..5589194cf3f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type.ts @@ -3,4 +3,5 @@ import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/f export type FlatEntityMaps = { byId: Partial>; idByUniversalIdentifier: Partial>; + universalIdentifiersByApplicationId: Partial>; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity.type.ts index 72359b083b8..26d0a17523b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/flat-entity.type.ts @@ -1,4 +1,22 @@ -export interface FlatEntity { +import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + +import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; +import { type NonNullableProperties } from 'src/types/non-nullable-properties.type'; + +export type SyncableEntityRelationProperties = + ExtractRecordTypeOrmRelationProperties; + +export interface FlatEntity + extends NonNullableProperties< + Omit + > { id: string; - universalIdentifier: string; + applicationId: string | null; } + +export type FlatEntityFrom< + TEntity extends SyncableEntity, + TEntityRelationProperties extends keyof TEntity, +> = FlatEntity & + Omit; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util.ts index 265f000a1e6..cee44fc384b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util.ts @@ -1,4 +1,4 @@ -import { isDefined } from 'class-validator'; +import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMapsException, @@ -32,5 +32,20 @@ export const addFlatEntityToFlatEntityMapsOrThrow = ({ ...flatEntityMaps.idByUniversalIdentifier, [flatEntity.universalIdentifier]: flatEntity.id, }, + universalIdentifiersByApplicationId: { + ...flatEntityMaps.universalIdentifiersByApplicationId, + ...(isDefined(flatEntity.applicationId) + ? { + [flatEntity.applicationId]: Array.from( + new Set([ + ...(flatEntityMaps.universalIdentifiersByApplicationId?.[ + flatEntity.applicationId + ] ?? []), + flatEntity.universalIdentifier, + ]), + ), + } + : {}), + }, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts index 6b82f103ecd..d002ce0b606 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util.ts @@ -1,3 +1,4 @@ +import isEmpty from 'lodash.isempty'; import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils'; import { @@ -31,10 +32,36 @@ export const deleteFlatEntityFromFlatEntityMapsOrThrow = < flatEntityMaps.idByUniversalIdentifier, ).filter(([_universalIdentifier, id]) => id !== entityToDeleteId); + const updatedUniversalIdentifiersByApplicationIdEntries = Object.entries( + flatEntityMaps.universalIdentifiersByApplicationId, + ) + .map(([applicationId, universalIdentifiers]) => { + const stillPresentUniversalIdentifiers = universalIdentifiers?.filter( + (universalIdentifier) => + updatedIdByUniversalIdentifierEntries.some( + ([existingUniversalIdentifier]) => + existingUniversalIdentifier === universalIdentifier, + ), + ); + + if ( + isDefined(stillPresentUniversalIdentifiers) || + isEmpty(stillPresentUniversalIdentifiers) + ) { + return undefined; + } + + return [applicationId, stillPresentUniversalIdentifiers]; + }) + .filter(isDefined); + return { byId: removePropertiesFromRecord(flatEntityMaps.byId, [entityToDeleteId]), idByUniversalIdentifier: Object.fromEntries( updatedIdByUniversalIdentifierEntries, ), + universalIdentifiersByApplicationId: Object.fromEntries( + updatedUniversalIdentifiersByApplicationIdEntries, + ), }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-flat-entities-by-application-id.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-flat-entities-by-application-id.util.ts new file mode 100644 index 00000000000..20cfa172ca5 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-flat-entities-by-application-id.util.ts @@ -0,0 +1,38 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; + +export const getFlatEntitiesByApplicationId = ( + maps: FlatEntityMaps, + applicationId: string, +): T[] => { + const universalIdentifiers = + maps.universalIdentifiersByApplicationId[applicationId]; + + if (!isDefined(universalIdentifiers)) { + return []; + } + + return universalIdentifiers + .map((universalId) => { + const id = maps.idByUniversalIdentifier[universalId]; + + if (!isDefined(id)) { + return undefined; + } + + const entity = maps.byId[id]; + + if (!isDefined(entity)) { + return undefined; + } + + if (entity.applicationId !== applicationId) { + return undefined; + } + + return entity; + }) + .filter(isDefined); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts index c08f5031ef4..cf32f3bff80 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock.ts @@ -43,7 +43,7 @@ export const getFlatFieldMetadataMock = ( standardId: null, standardOverrides: null, workspaceId: faker.string.uuid(), - + applicationId: faker.string.uuid(), relationTargetFieldMetadataId: null, relationTargetObjectMetadataId: null, ...overrides, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts index 2fb9e4a254d..0d6e89d595a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__mocks__/get-morph-or-relation-target-flat-field-metadata-mock.ts @@ -62,5 +62,6 @@ export const getRelationTargetFlatFieldMetadataMock = ({ ...overrides, defaultValue: null, options: null, + applicationId: faker.string.uuid(), }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap index 656a3bdd998..47b715525b3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap @@ -104,6 +104,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "result": { "flatFieldMetadatas": [ { + "applicationId": null, "createdAt": Any, "defaultValue": null, "description": "new field description", @@ -137,6 +138,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "workspaceId": Any, }, { + "applicationId": null, "createdAt": Any, "defaultValue": null, "description": null, @@ -172,6 +174,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "workspaceId": Any, }, { + "applicationId": null, "createdAt": Any, "defaultValue": null, "description": "new field description", @@ -205,6 +208,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "workspaceId": Any, }, { + "applicationId": null, "createdAt": Any, "defaultValue": null, "description": null, @@ -242,6 +246,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su ], "indexMetadatas": [ { + "applicationId": null, "createdAt": Any, "flatIndexFieldMetadatas": [ { @@ -265,6 +270,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "workspaceId": Any, }, { + "applicationId": null, "createdAt": Any, "flatIndexFieldMetadatas": [ { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts index 78380d955c7..47962b2d2ff 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type.ts @@ -9,6 +9,7 @@ export const FIELD_METADATA_RELATION_PROPERTIES = [ 'indexFieldMetadatas', 'object', 'viewFields', + 'application', 'viewFilters', 'viewGroups', ] as const satisfies (keyof FieldMetadataEntity)[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts index 5bf0d2fe251..0de64f1a0f8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util.ts @@ -44,6 +44,7 @@ export const generateIndexForFlatFieldMetadata = ({ universalIdentifier: indexId, updatedAt: createdAt, workspaceId, + applicationId: null, }, flatObjectMetadata, }, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts index cf6d3edf664..dde7f06855d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util.ts @@ -54,6 +54,7 @@ export const getDefaultFlatFieldMetadata = ({ updatedAt: createdAt, isUIReadOnly: createFieldInput.isUIReadOnly ?? false, morphId: null, + applicationId: null, viewFilterIds: [], viewGroupIds: [], } as const satisfies FlatFieldMetadata; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts index 0164bd3121b..1d4a8f1d46b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util.ts @@ -71,6 +71,7 @@ export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({ updatedAt: createdAt, deletedAt: null, viewId, + applicationId: toFlatFieldMetadata.applicationId, }); } else if (isDefined(emptyValueFlatViewGroup)) { sideEffectResult.flatViewGroupsToDelete.push(emptyValueFlatViewGroup); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts index 057624d98a2..2fe8aac6c5f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util.ts @@ -109,6 +109,7 @@ export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({ isVisible: true, fieldValue: option.value, position: viewGroupHighestPosition + createdOptionIndex + 1, + applicationId: fromFlatFieldMetadata.applicationId, }; }), ); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts index 0d0692d9ccf..48189fb4401 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock.ts @@ -23,6 +23,7 @@ export const getFlatIndexMetadataMock = ( name: 'defaultFlatIndexMetadataName', updatedAt: createdAt, workspaceId: faker.string.uuid(), + applicationId: faker.string.uuid(), ...overrides, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts index b8840c820a2..d1e67ba840a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type.ts @@ -1,3 +1,4 @@ +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; @@ -9,6 +10,7 @@ export type IndexMetadataRelationProperties = MetadataEntitiesRelationTarget >; +// Can't use FlatEntityFrom here because IndexFieldMetadataEntity is not a SyncableEntity export type FlatIndexFieldMetadata = Omit< IndexFieldMetadataEntity, ExtractRecordTypeOrmRelationProperties< @@ -17,7 +19,7 @@ export type FlatIndexFieldMetadata = Omit< > >; -export type FlatIndexMetadata = Omit< +export type FlatIndexMetadata = FlatEntityFrom< IndexMetadataEntity, IndexMetadataRelationProperties > & { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts index 965cccb2c02..98353da4c90 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts @@ -8,6 +8,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -53,24 +54,17 @@ export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceFlatMap relations: ['fields', 'indexMetadatas', 'views'], }); - const flatObjectMetadataMaps = objectMetadatas.reduce< - FlatEntityMaps - >((flatEntityMaps, object) => { - const flatObjectMetadata = - fromObjectMetadataEntityToFlatObjectMetadata(object); + return objectMetadatas.reduce( + (flatObjectMetadataMaps, objectMetadataEntity) => { + const flatObjectMetadata = + fromObjectMetadataEntityToFlatObjectMetadata(objectMetadataEntity); - return { - byId: { - ...flatEntityMaps.byId, - [flatObjectMetadata.id]: flatObjectMetadata, - }, - idByUniversalIdentifier: { - ...flatEntityMaps.idByUniversalIdentifier, - [flatObjectMetadata.universalIdentifier]: flatObjectMetadata.id, - }, - }; - }, EMPTY_FLAT_ENTITY_MAPS); - - return flatObjectMetadataMaps; + return addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatObjectMetadata, + flatEntityMaps: flatObjectMetadataMaps, + }); + }, + EMPTY_FLAT_ENTITY_MAPS, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts index 6b46f26214f..680118bbabe 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type.ts @@ -1,3 +1,4 @@ +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; import { type MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type'; @@ -18,11 +19,10 @@ type ObjectMetadataRelationProperties = ExtractRecordTypeOrmRelationProperties< MetadataEntitiesRelationTarget >; -export type FlatObjectMetadata = Omit< +export type FlatObjectMetadata = FlatEntityFrom< ObjectMetadataEntity, ObjectMetadataRelationProperties | 'dataSourceId' > & { - universalIdentifier: string; fieldMetadataIds: string[]; indexMetadataIds: string[]; viewIds: string[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts index 455b94cd0bc..10f2457a29c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util.ts @@ -48,6 +48,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre buildDefaultFlatFieldMetadatasForCustomObject({ flatObjectMetadata: { id: objectMetadataId, + applicationId: createObjectInput.applicationId ?? null, }, workspaceId, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts index 4826123cf3a..9f2544881a0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util.ts @@ -66,6 +66,7 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({ deletedAt: null, universalIdentifier: viewFieldId, aggregateOperation: null, + applicationId: existingFlatObjectMetadata.applicationId, }; accumulator.flatViewFieldToCreate.push(flatViewFieldToCreate); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts index d6c0dd54863..4eb1cff8e64 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/types/flat-role.type.ts @@ -1,3 +1,4 @@ +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; export const roleEntityRelationProperties = [ @@ -10,9 +11,7 @@ export const roleEntityRelationProperties = [ export type RoleEntityRelationProperties = (typeof roleEntityRelationProperties)[number]; -export type FlatRole = Omit< +export type FlatRole = FlatEntityFrom< RoleEntity, RoleEntityRelationProperties | 'createdAt' | 'updatedAt' -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts index 0224e6f23c3..d824ef0ed9a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util.ts @@ -20,5 +20,6 @@ export const fromRoleEntityToFlatRole = (role: RoleEntity): FlatRole => { canBeAssignedToApiKeys: role.canBeAssignedToApiKeys, workspaceId: role.workspaceId, universalIdentifier: role.standardId || role.id, + applicationId: role.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/types/flat-view-field.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/types/flat-view-field.type.ts index 35f4f7cfd49..5d5ee698366 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/types/flat-view-field.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/types/flat-view-field.type.ts @@ -1,5 +1,6 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; @@ -10,9 +11,7 @@ export type ViewFieldEntityRelationProperties = FieldMetadataEntity | ViewEntity | Workspace >; -export type FlatViewField = Omit< +export type FlatViewField = FlatEntityFrom< ViewFieldEntity, ViewFieldEntityRelationProperties -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts index 5eef9d23b48..ffcc2127280 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util.ts @@ -29,10 +29,12 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({ createdAt: createdAt, updatedAt: createdAt, deletedAt: null, - universalIdentifier: viewFieldId, + universalIdentifier: + createViewFieldInput.universalIdentifier ?? viewFieldId, isVisible: createViewFieldInput.isVisible ?? true, size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE, position: createViewFieldInput.position ?? 0, aggregateOperation: createViewFieldInput.aggregateOperation ?? null, + applicationId: createViewFieldInput.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type.ts index 9644f7636b0..26c93522b3f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type.ts @@ -1,5 +1,6 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; @@ -11,9 +12,7 @@ export type ViewFilterEntityRelationProperties = FieldMetadataEntity | ViewEntity | ViewFilterGroupEntity | Workspace >; -export type FlatViewFilter = Omit< +export type FlatViewFilter = FlatEntityFrom< ViewFilterEntity, ViewFilterEntityRelationProperties -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts index c6b0250beeb..5c1019d2a30 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util.ts @@ -36,12 +36,14 @@ export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({ createdAt: createdAt, updatedAt: createdAt, deletedAt: null, - universalIdentifier: viewFilterId, + universalIdentifier: + createViewFilterInput.universalIdentifier ?? viewFilterId, operand: createViewFilterInput.operand ?? ViewFilterOperand.CONTAINS, value: value, viewFilterGroupId: createViewFilterInput.viewFilterGroupId ?? null, positionInViewFilterGroup: createViewFilterInput.positionInViewFilterGroup ?? null, subFieldName: createViewFilterInput.subFieldName ?? null, + applicationId: createViewFilterInput.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/types/flat-view-group.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/types/flat-view-group.type.ts index bbd6e4d6f14..447c53905e5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/types/flat-view-group.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/types/flat-view-group.type.ts @@ -1,5 +1,6 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; @@ -10,9 +11,7 @@ export type ViewGroupEntityRelationProperties = FieldMetadataEntity | ViewEntity | Workspace >; -export type FlatViewGroup = Omit< +export type FlatViewGroup = FlatEntityFrom< ViewGroupEntity, ViewGroupEntityRelationProperties -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts index 1ab4cf106a3..85d8ab7fbc4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util.ts @@ -28,9 +28,11 @@ export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({ createdAt: createdAt, updatedAt: createdAt, deletedAt: null, - universalIdentifier: viewGroupId, + universalIdentifier: + createViewGroupInput.universalIdentifier ?? viewGroupId, isVisible: createViewGroupInput.isVisible ?? true, fieldValue: createViewGroupInput.fieldValue, position: createViewGroupInput.position ?? 0, + applicationId: createViewGroupInput.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/types/flat-view.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/types/flat-view.type.ts index 837a19c1dc2..1d76324a1b9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/types/flat-view.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/types/flat-view.type.ts @@ -1,4 +1,5 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; @@ -20,8 +21,10 @@ export type ViewEntityRelationProperties = | Workspace >; -export type FlatView = Omit & { - universalIdentifier: string; +export type FlatView = FlatEntityFrom< + ViewEntity, + ViewEntityRelationProperties +> & { viewFieldIds: string[]; viewFilterIds: string[]; viewGroupIds: string[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts index b36066d5270..5a84d0e382f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts @@ -20,9 +20,10 @@ export const fromCreateViewInputToFlatViewToCreate = ({ ); const createdAt = new Date(); + const viewId = createViewInput.id ?? v4(); return { - id: createViewInput.id ?? v4(), + id: viewId, objectMetadataId, workspaceId, name: createViewInput.name, @@ -42,9 +43,10 @@ export const fromCreateViewInputToFlatViewToCreate = ({ openRecordIn: createViewInput.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL, position: createViewInput.position ?? 0, type: createViewInput.type ?? ViewType.TABLE, - universalIdentifier: v4(), + universalIdentifier: createViewInput.universalIdentifier ?? viewId, viewFieldIds: [], viewFilterIds: [], viewGroupIds: [], + applicationId: createViewInput.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts index c06f2fe290f..fcb7b918a8f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.entity.ts @@ -2,7 +2,6 @@ import { Column, CreateDateColumn, Entity, - JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, @@ -11,8 +10,9 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type'; -import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; @@ -30,16 +30,16 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit 'namePlural', 'workspaceId', ]) -export class ObjectMetadataEntity implements Required { +export class ObjectMetadataEntity + extends SyncableEntity + implements Required +{ @PrimaryGeneratedColumn('uuid') id: string; @Column({ nullable: true, type: 'uuid' }) standardId: string | null; - @Column({ nullable: true, type: 'uuid' }) - applicationId: string | null; - @Column({ nullable: false, type: 'uuid' }) dataSourceId: string; @@ -137,13 +137,6 @@ export class ObjectMetadataEntity implements Required { @UpdateDateColumn({ type: 'timestamptz' }) updatedAt: Date; - @ManyToOne(() => ApplicationEntity, (application) => application.objects, { - onDelete: 'CASCADE', - nullable: true, - }) - @JoinColumn({ name: 'applicationId' }) - application: Relation | null; - @OneToMany( () => ObjectPermissionEntity, (objectPermission) => objectPermission.objectMetadata, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts index 5d5039e0436..d79e38db37a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts @@ -12,7 +12,7 @@ import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-mana type BuildDefaultFlatFieldMetadataForCustomObjectArgs = { workspaceId: string; - flatObjectMetadata: Pick; + flatObjectMetadata: Pick; }; export type DefaultFlatFieldForCustomObjectMaps = ReturnType< @@ -21,7 +21,7 @@ export type DefaultFlatFieldForCustomObjectMaps = ReturnType< // This could be replaced totally by an import schema + its transpilation when it's ready export const buildDefaultFlatFieldMetadatasForCustomObject = ({ workspaceId, - flatObjectMetadata: { id: objectMetadataId }, + flatObjectMetadata: { id: objectMetadataId, applicationId }, }: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => { const createdAt = new Date(); const idField: FlatFieldMetadata = { @@ -58,6 +58,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const nameField: FlatFieldMetadata = { @@ -94,6 +95,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const createdAtField: FlatFieldMetadata = { @@ -130,6 +132,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const updatedAtField: FlatFieldMetadata = { @@ -166,6 +169,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const deletedAtField: FlatFieldMetadata = { @@ -202,6 +206,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const createdByField: FlatFieldMetadata = { @@ -237,6 +242,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const positionField: FlatFieldMetadata = { @@ -273,6 +279,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ relationTargetObjectMetadataId: null, settings: null, morphId: null, + applicationId: applicationId ?? null, }; const searchVectorField: FlatFieldMetadata = { @@ -312,6 +319,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ generatedType: 'STORED', }, morphId: null, + applicationId: applicationId ?? null, }; return { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-index-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-index-for-custom-object.util.ts index 227e20a1b1a..ec617f404c6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-index-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-index-for-custom-object.util.ts @@ -44,6 +44,7 @@ export const buildDefaultIndexesForCustomObject = ({ universalIdentifier: tsFlatVectorIndexId, updatedAt: createdAt, workspaceId, + applicationId: flatObjectMetadata.applicationId ?? null, }, flatObjectMetadata, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts index 883d4a27279..2d6f1eb51f2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts @@ -97,6 +97,7 @@ const generateSourceFlatFieldMetadata = ({ ]), workspaceId, morphId: null, + applicationId: sourceFlatObjectMetadata.applicationId ?? null, }; }; @@ -165,6 +166,7 @@ const generateTargetFlatFieldMetadata = ({ targetFlatObjectMetadata.id, standardId, ]), + applicationId: sourceFlatObjectMetadata.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts index ed54b190c17..1e4f2f2d51d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts @@ -9,6 +9,8 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; @@ -16,7 +18,7 @@ import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets @Entity('role') @Unique('IDX_ROLE_LABEL_WORKSPACE_ID_UNIQUE', ['label', 'workspaceId']) -export class RoleEntity { +export class RoleEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/route-trigger/dtos/create-route-trigger.input.ts b/packages/twenty-server/src/engine/metadata-modules/route-trigger/dtos/create-route-trigger.input.ts index 54d5d0fdbfd..81b2351fe40 100644 --- a/packages/twenty-server/src/engine/metadata-modules/route-trigger/dtos/create-route-trigger.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/route-trigger/dtos/create-route-trigger.input.ts @@ -34,4 +34,7 @@ export class CreateRouteTriggerInput { @HideField() universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts index 997b98be3f1..b22e0d12c37 100644 --- a/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts @@ -9,6 +9,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { ROUTE_TRIGGER_ENTITY_RELATION_PROPERTIES, RouteTrigger, @@ -42,28 +43,19 @@ export class WorkspaceFlatRouteTriggerMapCacheService extends WorkspaceFlatMapCa }, }); - const flatRouteTriggerMaps = routeTriggers.reduce< - FlatEntityMaps - >((flatEntityMaps, routeTrigger) => { + return routeTriggers.reduce((flatRouteTriggerMaps, routeTriggerEntity) => { const flatRouteTrigger = { - ...removePropertiesFromRecord(routeTrigger, [ + ...removePropertiesFromRecord(routeTriggerEntity, [ ...ROUTE_TRIGGER_ENTITY_RELATION_PROPERTIES, ]), - universalIdentifier: routeTrigger.universalIdentifier ?? '', + universalIdentifier: + routeTriggerEntity.universalIdentifier ?? routeTriggerEntity.id, } satisfies FlatRouteTrigger; - return { - byId: { - ...flatEntityMaps.byId, - [flatRouteTrigger.id]: flatRouteTrigger, - }, - idByUniversalIdentifier: { - ...flatEntityMaps.idByUniversalIdentifier, - [flatRouteTrigger.universalIdentifier]: flatRouteTrigger.id, - }, - }; + return addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatRouteTrigger, + flatEntityMaps: flatRouteTriggerMaps, + }); }, EMPTY_FLAT_ENTITY_MAPS); - - return flatRouteTriggerMaps; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type.ts b/packages/twenty-server/src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type.ts index b9099af2d4c..98fbb1ee2a7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type.ts @@ -1,4 +1,5 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity'; import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; @@ -9,9 +10,7 @@ export type RouteTriggerEntityRelationProperties = ServerlessFunctionEntity | Workspace >; -export type FlatRouteTrigger = Omit< +export type FlatRouteTrigger = FlatEntityFrom< RouteTrigger, RouteTriggerEntityRelationProperties -> & { - universalIdentifier: string; -}; +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/route-trigger/utils/from-create-route-trigger-input-to-flat-route-trigger.util.ts b/packages/twenty-server/src/engine/metadata-modules/route-trigger/utils/from-create-route-trigger-input-to-flat-route-trigger.util.ts index 4a8b8db67e3..1dc5cdd98b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/route-trigger/utils/from-create-route-trigger-input-to-flat-route-trigger.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/route-trigger/utils/from-create-route-trigger-input-to-flat-route-trigger.util.ts @@ -23,5 +23,6 @@ export const fromCreateRouteTriggerInputToFlatRouteTrigger = ({ workspaceId, createdAt: now, updatedAt: now, + applicationId: createRouteTriggerInput.applicationId ?? null, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity.ts index cd4485ffa73..9ffa591de1a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity.ts @@ -3,7 +3,6 @@ import { CreateDateColumn, Entity, OneToMany, - OneToOne, PrimaryGeneratedColumn, Relation, UpdateDateColumn, @@ -11,7 +10,6 @@ import { import { PackageJson } from 'src/engine/core-modules/application/types/application.types'; import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; @Entity('serverlessFunctionLayer') export class ServerlessFunctionLayerEntity { @@ -39,15 +37,6 @@ export class ServerlessFunctionLayerEntity { ) serverlessFunctions: Relation; - @OneToOne( - () => ApplicationEntity, - (application) => application.serverlessFunctionLayer, - { - nullable: true, - }, - ) - application: Relation | null; - @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts index b600d9adf3b..79828197183 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.entity.ts @@ -18,9 +18,8 @@ import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/inte import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity'; import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity'; import { RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity'; -import { ServerlessFunctionEntityRelationProperties } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type'; -import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity'; +import { ServerlessFunctionEntityRelationProperties } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type'; const DEFAULT_SERVERLESS_TIMEOUT_SECONDS = 300; // 5 minutes @@ -66,9 +65,6 @@ export class ServerlessFunctionEntity @Column({ nullable: false, type: 'uuid' }) workspaceId: string; - @Column({ nullable: true, type: 'uuid' }) - applicationId: string | null; - @Column({ nullable: true, type: 'text' }) checksum: string | null; @@ -84,17 +80,6 @@ export class ServerlessFunctionEntity @JoinColumn({ name: 'serverlessFunctionLayerId' }) serverlessFunctionLayer: Relation; - @ManyToOne( - () => ApplicationEntity, - (application) => application.serverlessFunctions, - { - onDelete: 'CASCADE', - nullable: true, - }, - ) - @JoinColumn({ name: 'applicationId' }) - application: Relation | null; - @OneToMany( () => CronTrigger, (cronTrigger) => cronTrigger.serverlessFunction, diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts index 4ccf324728a..d7416e1600f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts @@ -9,6 +9,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES, ServerlessFunctionEntity, @@ -43,29 +44,23 @@ export class WorkspaceFlatServerlessFunctionMapCacheService extends WorkspaceFla withDeleted: true, }); - const flatServerlessFunctionMaps = serverlessFunctions.reduce< - FlatEntityMaps - >((flatEntityMaps, serverlessFunction) => { - const flatServerlessFunction = { - ...removePropertiesFromRecord(serverlessFunction, [ - ...SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES, - ]), - universalIdentifier: serverlessFunction.universalIdentifier ?? '', - } satisfies FlatServerlessFunction; + return serverlessFunctions.reduce( + (flatServerlessFunctionMaps, serverlessFunctionEntity) => { + const flatServerlessFunction = { + ...removePropertiesFromRecord(serverlessFunctionEntity, [ + ...SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES, + ]), + universalIdentifier: + serverlessFunctionEntity.universalIdentifier ?? + serverlessFunctionEntity.id, + } satisfies FlatServerlessFunction; - return { - byId: { - ...flatEntityMaps.byId, - [flatServerlessFunction.id]: flatServerlessFunction, - }, - idByUniversalIdentifier: { - ...flatEntityMaps.idByUniversalIdentifier, - [flatServerlessFunction.universalIdentifier]: - flatServerlessFunction.id, - }, - }; - }, EMPTY_FLAT_ENTITY_MAPS); - - return flatServerlessFunctionMaps; + return addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatServerlessFunction, + flatEntityMaps: flatServerlessFunctionMaps, + }); + }, + EMPTY_FLAT_ENTITY_MAPS, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type.ts index 75573aa73ee..21c7e73feb9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type.ts @@ -1,12 +1,12 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity'; import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity'; +import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type'; import { type RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity'; -import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; -import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { type ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity'; +import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { type ServerlessFunctionCode } from 'src/engine/metadata-modules/serverless-function/types/serverless-function-code.type'; +import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type'; export type ServerlessFunctionEntityRelationProperties = ExtractRecordTypeOrmRelationProperties< @@ -15,14 +15,12 @@ export type ServerlessFunctionEntityRelationProperties = | DatabaseEventTrigger | RouteTrigger | Workspace - | ApplicationEntity | ServerlessFunctionLayerEntity >; -export type FlatServerlessFunction = Omit< +export type FlatServerlessFunction = FlatEntityFrom< ServerlessFunctionEntity, ServerlessFunctionEntityRelationProperties > & { - universalIdentifier: string; code?: ServerlessFunctionCode; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input.ts index cd81a2c1204..cb6fca53796 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input.ts @@ -1,4 +1,4 @@ -import { Field, InputType } from '@nestjs/graphql'; +import { Field, HideField, InputType } from '@nestjs/graphql'; import { IsBoolean, @@ -45,4 +45,10 @@ export class CreateViewFieldInput { @IsEnum(AggregateOperations) @Field(() => AggregateOperations, { nullable: true }) aggregateOperation?: AggregateOperations; + + @HideField() + universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input.ts index 5885377cd53..b4b71caf913 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input.ts @@ -1,7 +1,5 @@ -import { Field, InputType } from '@nestjs/graphql'; +import { Field, HideField, InputType } from '@nestjs/graphql'; -import GraphQLJSON from 'graphql-type-json'; -import { ViewFilterOperand } from 'twenty-shared/types'; import { IsDefined, IsEnum, @@ -10,6 +8,8 @@ import { IsString, IsUUID, } from 'class-validator'; +import GraphQLJSON from 'graphql-type-json'; +import { ViewFilterOperand } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type'; @@ -52,4 +52,10 @@ export class CreateViewFilterInput { @IsUUID() @Field(() => UUIDScalarType, { nullable: false }) viewId: string; + + @HideField() + universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input.ts index f7747d548e2..66e72d314a5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input.ts @@ -1,4 +1,4 @@ -import { Field, InputType } from '@nestjs/graphql'; +import { Field, HideField, InputType } from '@nestjs/graphql'; import { IsBoolean, @@ -38,4 +38,10 @@ export class CreateViewGroupInput { @IsUUID() @Field(() => UUIDScalarType, { nullable: false }) viewId: string; + + @HideField() + universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/entities/view-group.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/entities/view-group.entity.ts index 64b742b674c..44d9947c20b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/entities/view-group.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/entities/view-group.entity.ts @@ -22,7 +22,10 @@ import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entit @Index('IDX_VIEW_GROUP_VIEW_ID', ['viewId'], { where: '"deletedAt" IS NULL', }) -export class ViewGroupEntity extends SyncableEntity { +export class ViewGroupEntity + extends SyncableEntity + implements Required +{ @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/view/dtos/inputs/create-view.input.ts b/packages/twenty-server/src/engine/metadata-modules/view/dtos/inputs/create-view.input.ts index d68dc56e7d7..847787e6b9b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/dtos/inputs/create-view.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/dtos/inputs/create-view.input.ts @@ -1,4 +1,4 @@ -import { Field, InputType } from '@nestjs/graphql'; +import { Field, HideField, InputType } from '@nestjs/graphql'; import { IsBoolean, @@ -90,4 +90,10 @@ export class CreateViewInput { @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) calendarFieldMetadataId?: string; + + @HideField() + universalIdentifier?: string; + + @HideField() + applicationId?: string; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts index 5fbe9b14c45..9b7545678fa 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts @@ -127,6 +127,14 @@ export class WorkspaceMigrationBuildOrchestratorService { ...flatFieldMetadataMaps?.from.idByUniversalIdentifier, ...flatFieldMetadataMaps?.to.idByUniversalIdentifier, }, + universalIdentifiersByApplicationId: { + ...dependencyAllFlatEntityMaps?.flatFieldMetadataMaps + ?.universalIdentifiersByApplicationId, + ...flatFieldMetadataMaps?.from + .universalIdentifiersByApplicationId, + ...flatFieldMetadataMaps?.to + .universalIdentifiersByApplicationId, + }, }, }, /// diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface.ts index 1b218b91ffe..9f9aa6e45e7 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface.ts @@ -8,6 +8,8 @@ export type PartialIndexMetadata = Omit< | 'createdAt' | 'updatedAt' | 'universalIdentifier' + | 'application' + | 'applicationId' > & { columns: string[]; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role.ts index 5a5ccba4082..42f8b1b959f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role.ts @@ -15,4 +15,5 @@ export const ADMIN_ROLE: StandardRoleDefinition = { canBeAssignedToUsers: true, canBeAssignedToAgents: false, canBeAssignedToApiKeys: true, + applicationId: null, // TODO: Replace with Twenty application ID }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-manipulator-role.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-manipulator-role.ts index 6c3b4cc0275..2c5f043c5b1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-manipulator-role.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-manipulator-role.ts @@ -15,4 +15,5 @@ export const DATA_MANIPULATOR_ROLE: StandardRoleDefinition = { canBeAssignedToUsers: false, canBeAssignedToAgents: true, canBeAssignedToApiKeys: false, + applicationId: null, // TODO: Replace with Twenty application ID }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-navigator-role.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-navigator-role.ts index 3370a066932..e91468b4e18 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-navigator-role.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-navigator-role.ts @@ -15,4 +15,5 @@ export const DATA_NAVIGATOR_ROLE: StandardRoleDefinition = { canBeAssignedToUsers: false, canBeAssignedToAgents: true, canBeAssignedToApiKeys: false, + applicationId: null, // TODO: Replace with Twenty application ID }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role.ts index ec8565b27e7..dd8150a3d3a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/workflow-manager-role.ts @@ -17,4 +17,5 @@ export const WORKFLOW_MANAGER_ROLE: StandardRoleDefinition = { canBeAssignedToAgents: true, canBeAssignedToApiKeys: false, permissionFlags: [PermissionFlagType.WORKFLOWS], + applicationId: null, // TODO: Replace with Twenty application ID }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface.ts index f97298050e1..a0f03a66d52 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface.ts @@ -1,4 +1,6 @@ -import { Column, Index } from 'typeorm'; +import { Column, Index, JoinColumn, ManyToOne, Relation } from 'typeorm'; + +import type { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; @Index(['workspaceId', 'universalIdentifier'], { unique: true, @@ -6,4 +8,14 @@ import { Column, Index } from 'typeorm'; export abstract class SyncableEntity { @Column({ nullable: true, type: 'uuid' }) universalIdentifier: string | null; + + @Column({ nullable: true, type: 'uuid' }) + applicationId: string | null; + + @ManyToOne('ApplicationEntity', { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ name: 'applicationId' }) + application: Relation; } diff --git a/packages/twenty-server/src/types/non-nullable-properties.type.ts b/packages/twenty-server/src/types/non-nullable-properties.type.ts new file mode 100644 index 00000000000..ccc19d142cb --- /dev/null +++ b/packages/twenty-server/src/types/non-nullable-properties.type.ts @@ -0,0 +1,3 @@ +export type NonNullableProperties = { + [P in keyof T]: NonNullable; +}; diff --git a/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts b/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts index a9ec4b41207..6f0b6676168 100644 --- a/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts +++ b/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts @@ -1,6 +1,7 @@ import { faker } from '@faker-js/faker'; import { type FieldMetadataType } from 'twenty-shared/types'; +import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -49,6 +50,9 @@ export const getMockFieldMetadataEntity = < createdAt: new Date(), updatedAt: new Date(), isActive: true, + application: {} as ApplicationEntity, + applicationId: faker.string.uuid(), + universalIdentifier: faker.string.uuid(), ...overrides, }; }; diff --git a/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts b/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts index 255facf0f9b..60dfb031644 100644 --- a/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts +++ b/packages/twenty-server/src/utils/__test__/get-object-metadata-entity.mock.ts @@ -1,5 +1,6 @@ import { faker } from '@faker-js/faker'; +import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -41,8 +42,9 @@ export const getMockObjectMetadataEntity = ( objectPermissions: [], shortcut: null, standardId: null, - applicationId: null, - application: null, + universalIdentifier: faker.string.uuid(), + applicationId: faker.string.uuid(), + application: {} as ApplicationEntity, targetRelationFields: [], standardOverrides: null, targetTableName: faker.string.uuid(), diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts index 7fff249c73a..331ab756028 100644 --- a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts @@ -5,6 +5,7 @@ import { type Repository } from 'typeorm'; import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service'; import { ToolService } from 'src/engine/core-modules/ai/services/tool.service'; +import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service'; import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service'; import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service'; @@ -210,7 +211,10 @@ export const createAgentToolTestModule = icon: 'IconTest', isCustom: false, applicationId: null, - application: null, + application: {} as ApplicationEntity, + standardId: null, + deletedAt: null, + universalIdentifier: testAgentId, description: 'Test agent for integration tests', prompt: 'You are a test agent', modelId: 'gpt-4o',