From af6fc1e9eff8dafedb3220ab8f73846ecbdc6e5b Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 28 Aug 2025 17:33:39 +0200 Subject: [PATCH] Add universal identifier to view tables (#14139) ## Context Introducing a new SyncableEntity abstraction that can be extended by Entities that we want to "sync" via their universal identifier (the class will add this uuid to the entity). Starting with views, will refactor existing syncable entities such as objects/fields later --- ...1720-addUniversalIdentifierToViewTables.ts | 85 +++++++++++++++++++ .../view/entities/view-field.entity.ts | 4 +- .../view/entities/view-filter-group.entity.ts | 4 +- .../view/entities/view-filter.entity.ts | 4 +- .../view/entities/view-group.entity.ts | 4 +- .../view/entities/view-sort.entity.ts | 4 +- .../core-modules/view/entities/view.entity.ts | 4 +- .../interfaces/syncable-entity.interface.ts | 9 ++ 8 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1756393721720-addUniversalIdentifierToViewTables.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface.ts diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1756393721720-addUniversalIdentifierToViewTables.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1756393721720-addUniversalIdentifierToViewTables.ts new file mode 100644 index 0000000000..00ba7fa6e6 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1756393721720-addUniversalIdentifierToViewTables.ts @@ -0,0 +1,85 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddUniversalIdentifierToViewTables1756393721720 + implements MigrationInterface +{ + name = 'AddUniversalIdentifierToViewTables1756393721720'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."viewFilter" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewGroup" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ADD "universalIdentifier" uuid`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_cd4588bfc9ad73345b3953a039" ON "core"."viewFilter" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e6ed40a61e4584e98584019a47" ON "core"."viewFilterGroup" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_a44e3b03f0eca32d0504d5ef73" ON "core"."viewGroup" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_38232fc0c6567ed029c2b1a12c" ON "core"."viewSort" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_552aa6908966e980099b3e5ebf" ON "core"."view" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_b86af4ea24cae518dee8eae996" ON "core"."viewField" ("workspaceId", "universalIdentifier") `, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DROP INDEX "core"."IDX_b86af4ea24cae518dee8eae996"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_552aa6908966e980099b3e5ebf"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_38232fc0c6567ed029c2b1a12c"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_a44e3b03f0eca32d0504d5ef73"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e6ed40a61e4584e98584019a47"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_cd4588bfc9ad73345b3953a039"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewGroup" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" DROP COLUMN "universalIdentifier"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilter" DROP COLUMN "universalIdentifier"`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view-field.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view-field.entity.ts index be23f0db82..e1c366ca17 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view-field.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view-field.entity.ts @@ -11,6 +11,8 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -26,7 +28,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ where: '"deletedAt" IS NULL', }, ) -export class ViewFieldEntity { +export class ViewFieldEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view-filter-group.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view-filter-group.entity.ts index bca2ae821f..65a92e7ff7 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view-filter-group.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view-filter-group.entity.ts @@ -12,6 +12,8 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { ViewFilterEntity } from 'src/engine/core-modules/view/entities/view-filter.entity'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { ViewFilterGroupLogicalOperator } from 'src/engine/core-modules/view/enums/view-filter-group-logical-operator'; @@ -19,7 +21,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @Entity({ name: 'viewFilterGroup', schema: 'core' }) @Index('IDX_VIEW_FILTER_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) -export class ViewFilterGroupEntity { +export class ViewFilterGroupEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view-filter.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view-filter.entity.ts index 70b9d7fc99..13ad44961b 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view-filter.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view-filter.entity.ts @@ -11,6 +11,8 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { ViewFilterGroupEntity } from 'src/engine/core-modules/view/entities/view-filter-group.entity'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { ViewFilterOperand } from 'src/engine/core-modules/view/enums/view-filter-operand'; @@ -21,7 +23,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ @Entity({ name: 'viewFilter', schema: 'core' }) @Index('IDX_VIEW_FILTER_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) @Index('IDX_VIEW_FILTER_FIELD_METADATA_ID', ['fieldMetadataId']) -export class ViewFilterEntity { +export class ViewFilterEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view-group.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view-group.entity.ts index f40b080540..7948c45465 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view-group.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view-group.entity.ts @@ -11,13 +11,15 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @Entity({ name: 'viewGroup', schema: 'core' }) @Index('IDX_VIEW_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) -export class ViewGroupEntity { +export class ViewGroupEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view-sort.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view-sort.entity.ts index 1e984cd238..71a4e788af 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view-sort.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view-sort.entity.ts @@ -11,6 +11,8 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { ViewSortDirection } from 'src/engine/core-modules/view/enums/view-sort-direction'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -26,7 +28,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ where: '"deletedAt" IS NULL', }, ) -export class ViewSortEntity { +export class ViewSortEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts index 625751ce78..ffd04f57ed 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts @@ -12,6 +12,8 @@ import { UpdateDateColumn, } from 'typeorm'; +import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface'; + import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; import { ViewFilterGroupEntity } from 'src/engine/core-modules/view/entities/view-filter-group.entity'; @@ -29,7 +31,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat 'workspaceId', 'objectMetadataId', ]) -export class ViewEntity { +export class ViewEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; 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 new file mode 100644 index 0000000000..d09b12a8ff --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface.ts @@ -0,0 +1,9 @@ +import { Column, Index } from 'typeorm'; + +@Index(['workspaceId', 'universalIdentifier'], { + unique: true, +}) +export abstract class SyncableEntity { + @Column({ nullable: true, type: 'uuid' }) + universalIdentifier?: string | null; +}