From 596b7cc62dff8d2e0271439284a157b0306f0dbb Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:23:55 +0100 Subject: [PATCH] Deprecate nullable `syncableEntity` (#17279) # Introduction As we've been identifying both standard and custom entities for all the metadata that had standard We now still need to identify all custom entities enforcing them to have an `applicationId` and `universalIdentifier` In this PR we've removed the `SyncableEntityRequired` in favor requiring props directly in the `SyncableEntity` Which means that all metadata in db will now expect non nullable applicationId and universalIdentifier across the whole application Will add some type cleanup later in https://github.com/twentyhq/twenty/pull/17277 --- ...ify-remaining-entities-metadata.command.ts | 191 ++++++++++++ ...ation-id-not-nullable-migration.command.ts | 71 +++++ .../1-16-upgrade-version-command.module.ts | 6 + .../upgrade.command.ts | 7 + ...alIdentifierAndApplicationIdNotNullable.ts | 292 ++++++++++++++++++ ...ntifierAndApplicationIdNotNullable.util.ts | 239 ++++++++++++++ .../ai/ai-agent/entities/agent.entity.ts | 4 +- .../entities/command-menu-item.entity.ts | 4 +- .../field-metadata/field-metadata.entity.ts | 4 +- ...rmission-predicate-group-to-create.util.ts | 4 +- ...vel-permission-predicate-to-create.util.ts | 4 +- .../entities/front-component.entity.ts | 4 +- .../index-metadata/index-metadata.entity.ts | 4 +- .../object-metadata/object-metadata.entity.ts | 4 +- .../metadata-modules/role/role.entity.ts | 7 +- .../row-level-permission.module.ts | 2 + ...evel-permission-predicate-group.service.ts | 9 + .../row-level-permission-predicate.service.ts | 25 +- .../view-field/entities/view-field.entity.ts | 4 +- .../entities/view-filter.entity.ts | 4 +- .../view-group/entities/view-group.entity.ts | 4 +- .../services/tests/view-sort.service.spec.ts | 43 ++- .../view-sort/services/view-sort.service.ts | 17 +- .../view-sort/view-sort.module.ts | 2 + .../view/entities/view.entity.ts | 7 +- .../syncable-entity-required.interface.ts | 22 -- .../types/syncable-entity.interface.ts | 9 +- ...-step-operations.workspace-service.spec.ts | 4 +- 28 files changed, 934 insertions(+), 63 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration.command.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/utils/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.util.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/types/syncable-entity-required.interface.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command.ts new file mode 100644 index 0000000000..e563c74a43 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command.ts @@ -0,0 +1,191 @@ +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { type AllMetadataName } from 'twenty-shared/metadata'; +import { DataSource, IsNull, Repository } from 'typeorm'; +import { v4 } from 'uuid'; + +import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { ALL_METADATA_ENTITY_BY_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant'; +import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; +import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util'; +import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; + +const REMAINING_ENTITIES_METADATA_NAMES = [ + 'roleTarget', + 'rowLevelPermissionPredicate', + 'rowLevelPermissionPredicateGroup', + 'viewFilterGroup', + 'cronTrigger', + 'databaseEventTrigger', + 'routeTrigger', + 'serverlessFunction', + 'skill', + 'pageLayoutWidget', + 'pageLayout', + 'pageLayoutTab', +] as const satisfies AllMetadataName[]; + +@Command({ + name: 'upgrade:1-16:identify-remaining-entities-metadata', + description: + 'Identify remaining entities metadata (roleTarget, rowLevelPermissionPredicate, rowLevelPermissionPredicateGroup, viewFilterGroup, viewSort, cronTrigger, databaseEventTrigger, routeTrigger, serverlessFunction, skill, pageLayoutWidget, pageLayout, pageLayoutTab)', +}) +export class IdentifyRemainingEntitiesMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + protected readonly applicationService: ApplicationService, + protected readonly workspaceCacheService: WorkspaceCacheService, + @InjectDataSource() + private readonly coreDataSource: DataSource, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running identify remaining entities metadata for workspace ${workspaceId}`, + ); + + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + for (const metadataName of REMAINING_ENTITIES_METADATA_NAMES) { + await this.identifyEntitiesForMetadataName({ + metadataName, + workspaceId, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + dryRun: options.dryRun ?? false, + }); + } + + await this.identifyViewSortEntities({ + workspaceId, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, + dryRun: options.dryRun ?? false, + }); + } + + private async identifyEntitiesForMetadataName({ + metadataName, + workspaceId, + workspaceCustomApplicationId, + dryRun, + }: { + metadataName: AllMetadataName; + workspaceId: string; + workspaceCustomApplicationId: string; + dryRun: boolean; + }): Promise { + const entityClass = ALL_METADATA_ENTITY_BY_METADATA_NAME[metadataName]; + const repository = this.coreDataSource.getRepository(entityClass); + + const entitiesWithoutApplicationId = await repository.find({ + select: ['id', 'universalIdentifier', 'applicationId'], + where: { + workspaceId, + applicationId: IsNull(), + }, + }); + + if (entitiesWithoutApplicationId.length === 0) { + this.logger.log( + `No ${metadataName} entities found without applicationId for workspace ${workspaceId}`, + ); + + return; + } + + const updates = entitiesWithoutApplicationId.map( + (entity: SyncableEntity & { id: string }) => ({ + id: entity.id, + universalIdentifier: entity.universalIdentifier ?? v4(), + applicationId: workspaceCustomApplicationId, + }), + ); + + this.logger.log( + `Found ${updates.length} ${metadataName} entities to update for workspace ${workspaceId}`, + ); + + if (!dryRun) { + await repository.save(updates); + } + + const relatedMetadataNames = getMetadataRelatedMetadataNames(metadataName); + const relatedCacheKeysToInvalidate = relatedMetadataNames.map( + getMetadataFlatEntityMapsKey, + ); + + const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName); + + this.logger.log( + `Invalidating caches: ${flatEntityMapsKey} ${relatedCacheKeysToInvalidate.join(' ')}`, + ); + + if (!dryRun) { + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + flatEntityMapsKey, + ...relatedCacheKeysToInvalidate, + ]); + } + } + + private async identifyViewSortEntities({ + workspaceId, + workspaceCustomApplicationId, + dryRun, + }: { + workspaceId: string; + workspaceCustomApplicationId: string; + dryRun: boolean; + }): Promise { + const viewSortRepository = + this.coreDataSource.getRepository(ViewSortEntity); + + const viewSortsWithoutApplicationId = await viewSortRepository.find({ + select: ['id', 'universalIdentifier', 'applicationId'], + where: { + workspaceId, + applicationId: IsNull(), + }, + }); + + if (viewSortsWithoutApplicationId.length === 0) { + this.logger.log( + `No viewSort entities found without applicationId for workspace ${workspaceId}`, + ); + + return; + } + + const updates = viewSortsWithoutApplicationId.map((viewSort) => ({ + id: viewSort.id, + universalIdentifier: viewSort.universalIdentifier ?? v4(), + applicationId: workspaceCustomApplicationId, + })); + + this.logger.log( + `Found ${updates.length} viewSort entities to update for workspace ${workspaceId}`, + ); + + if (!dryRun) { + await viewSortRepository.save(updates); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration.command.ts new file mode 100644 index 0000000000..3bfe2c7bea --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration.command.ts @@ -0,0 +1,71 @@ +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { DataSource, Repository } from 'typeorm'; + +import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.util'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; + +@Command({ + name: 'upgrade:1-16:make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration', + description: + 'Make universalIdentifier and applicationId columns NOT NULL on remaining entities (roleTarget, rowLevelPermissionPredicate, rowLevelPermissionPredicateGroup, viewFilterGroup, viewSort, cronTrigger, databaseEventTrigger, routeTrigger, serverlessFunction, skill, pageLayoutWidget, pageLayout, pageLayoutTab)', +}) +export class MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + private hasRunOnce = false; + + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + @InjectDataSource() + private readonly coreDataSource: DataSource, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + options, + }: RunOnWorkspaceArgs): Promise { + if (this.hasRunOnce) { + this.logger.warn( + 'Skipping has already been run once MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand', + ); + + return; + } + + if (options.dryRun) { + return; + } + + const queryRunner = this.coreDataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + await makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableQueries( + queryRunner, + ); + + await queryRunner.commitTransaction(); + this.logger.log( + 'Successfully run MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand', + ); + this.hasRunOnce = true; + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error( + `Rolling back MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: ${error.message}`, + ); + } finally { + await queryRunner.release(); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts index 1724092f8e..17d3dd23d8 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module.ts @@ -16,6 +16,8 @@ import { MakeAgentUniversalIdentifierAndApplicationIdNotNullableMigrationCommand import { MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-field-metadata-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-index-metadata-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-object-metadata-universal-identifier-and-application-id-not-nullable-migration.command'; +import { IdentifyRemainingEntitiesMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command'; +import { MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeRoleUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-role-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-field-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-filter-universal-identifier-and-application-id-not-nullable-migration.command'; @@ -86,6 +88,8 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace MakeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, MakeViewGroupUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, MakeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + IdentifyRemainingEntitiesMetadataCommand, ], exports: [ UpdateTaskOnDeleteActionCommand, @@ -109,6 +113,8 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace MakeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, MakeViewGroupUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, MakeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + IdentifyRemainingEntitiesMetadataCommand, ], }) export class V1_16_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index a27e372127..c96eb18a05 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -31,12 +31,14 @@ import { IdentifyObjectMetadataCommand } from 'src/database/commands/upgrade-ver import { IdentifyRoleMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-role-metadata.command'; import { IdentifyViewFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command'; import { IdentifyViewFilterMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-filter-metadata.command'; +import { IdentifyRemainingEntitiesMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command'; import { IdentifyViewGroupMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-group-metadata.command'; import { IdentifyViewMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command'; import { MakeAgentUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-agent-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-field-metadata-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-index-metadata-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-object-metadata-universal-identifier-and-application-id-not-nullable-migration.command'; +import { MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-remaining-entities-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeRoleUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-role-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-field-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-filter-universal-identifier-and-application-id-not-nullable-migration.command'; @@ -103,6 +105,8 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly makeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, protected readonly makeViewGroupUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeViewGroupUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, protected readonly makeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + protected readonly identifyRemainingEntitiesMetadataCommand: IdentifyRemainingEntitiesMetadataCommand, + protected readonly makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ) { super( workspaceRepository, @@ -167,6 +171,9 @@ export class UpgradeCommand extends UpgradeCommandRunner { .makeViewGroupUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, this .makeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + this.identifyRemainingEntitiesMetadataCommand, + this + .makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ]; this.allCommands = { diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.ts new file mode 100644 index 0000000000..e90a275cc0 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.ts @@ -0,0 +1,292 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +import { makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.util'; + +export class MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable1768916632478 + implements MigrationInterface +{ + name = + 'MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable1768916632478'; + + public async up(queryRunner: QueryRunner): Promise { + const savepointName = + 'sp_make_remaining_entities_universal_identifier_and_application_id_not_nullable'; + + try { + await queryRunner.query(`SAVEPOINT ${savepointName}`); + + await makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableQueries( + queryRunner, + ); + + await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`); + } catch (e) { + try { + await queryRunner.query(`ROLLBACK TO SAVEPOINT ${savepointName}`); + await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`); + } catch (rollbackError) { + // eslint-disable-next-line no-console + console.error( + 'Failed to rollback to savepoint in MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable1768916632478', + rollbackError, + ); + throw rollbackError; + } + + // eslint-disable-next-line no-console + console.error( + 'Swallowing MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable1768916632478 error', + e, + ); + } + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" DROP CONSTRAINT "FK_4493447c2e4029aa26cabf30460"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_3763c4e8f942ff1e24040a13a9"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_3763c4e8f942ff1e24040a13a9" ON "core"."pageLayoutTab" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" ADD CONSTRAINT "FK_4493447c2e4029aa26cabf30460" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" DROP CONSTRAINT "FK_5e7f19b88c0864db19e2bad0fc5"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_256fabec226411154baba649df"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_256fabec226411154baba649df" ON "core"."pageLayout" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ADD CONSTRAINT "FK_5e7f19b88c0864db19e2bad0fc5" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" DROP CONSTRAINT "FK_fb84d310b4cfe5916ced6fc3e2a"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_2a33a0e7e44c393ca7bb578dae"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_2a33a0e7e44c393ca7bb578dae" ON "core"."pageLayoutWidget" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ADD CONSTRAINT "FK_fb84d310b4cfe5916ced6fc3e2a" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."skill" DROP CONSTRAINT "FK_46f69b93b58666bb388c5c7785a"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e6398c21e6bb31b525272fac84"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e6398c21e6bb31b525272fac84" ON "core"."skill" ("universalIdentifier", "workspaceId") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" ADD CONSTRAINT "FK_46f69b93b58666bb388c5c7785a" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" DROP CONSTRAINT "FK_62cbd26626ff76df897181c7994"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_5b43e65e322d516c9307bed97a"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_5b43e65e322d516c9307bed97a" ON "core"."serverlessFunction" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" ADD CONSTRAINT "FK_62cbd26626ff76df897181c7994" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" DROP CONSTRAINT "FK_6edf47a8bfe17a5811998dc7162"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e9c53b9ac5035d3202a8737020"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e9c53b9ac5035d3202a8737020" ON "core"."routeTrigger" ("universalIdentifier", "workspaceId") `, + ); + 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`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" DROP CONSTRAINT "FK_9acc2804037a5c885633024368d"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_960465af116edf9ac501bfb3db"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_960465af116edf9ac501bfb3db" ON "core"."databaseEventTrigger" ("universalIdentifier", "workspaceId") `, + ); + 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"."cronTrigger" DROP CONSTRAINT "FK_817ea28e71e3b19acc258dd7dcd"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_8adc1fd6cb0dad2fbfd945954d"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_8adc1fd6cb0dad2fbfd945954d" ON "core"."cronTrigger" ("universalIdentifier", "workspaceId") `, + ); + 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"."viewSort" DROP CONSTRAINT "FK_ff8cbebe1704954120df82bf393"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_38232fc0c6567ed029c2b1a12c"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_38232fc0c6567ed029c2b1a12c" ON "core"."viewSort" ("universalIdentifier", "workspaceId") `, + ); + 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"."viewFilterGroup" DROP CONSTRAINT "FK_bfc3498b964ef1bfc89b1f2bee3"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e6ed40a61e4584e98584019a47"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e6ed40a61e4584e98584019a47" ON "core"."viewFilterGroup" ("universalIdentifier", "workspaceId") `, + ); + 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"."rowLevelPermissionPredicateGroup" DROP CONSTRAINT "FK_1e82563accb67114f65a3993b86"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_a14b5665091e86d461fb585924"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_a14b5665091e86d461fb585924" ON "core"."rowLevelPermissionPredicateGroup" ("universalIdentifier", "workspaceId") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ADD CONSTRAINT "FK_1e82563accb67114f65a3993b86" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_23b36d07d363f81200654fa1334"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e46f3e01227f1c8ee0c8041821"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e46f3e01227f1c8ee0c8041821" ON "core"."rowLevelPermissionPredicate" ("universalIdentifier", "workspaceId") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_23b36d07d363f81200654fa1334" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" DROP CONSTRAINT "FK_b1db027b64f44029389ace305ac"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_0082568653b80c15903c5a2ba9"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_0082568653b80c15903c5a2ba9" ON "core"."roleTarget" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" ADD CONSTRAINT "FK_b1db027b64f44029389ace305ac" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.util.ts b/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.util.ts new file mode 100644 index 0000000000..8b19cee410 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768916632478-makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullable.util.ts @@ -0,0 +1,239 @@ +import { type QueryRunner } from 'typeorm'; + +export const makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableQueries = + async (queryRunner: QueryRunner): Promise => { + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" DROP CONSTRAINT "FK_b1db027b64f44029389ace305ac"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_0082568653b80c15903c5a2ba9"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" DROP CONSTRAINT "FK_23b36d07d363f81200654fa1334"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e46f3e01227f1c8ee0c8041821"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" DROP CONSTRAINT "FK_1e82563accb67114f65a3993b86"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_a14b5665091e86d461fb585924"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" DROP CONSTRAINT "FK_bfc3498b964ef1bfc89b1f2bee3"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e6ed40a61e4584e98584019a47"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewFilterGroup" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" DROP CONSTRAINT "FK_ff8cbebe1704954120df82bf393"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_38232fc0c6567ed029c2b1a12c"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewSort" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" DROP CONSTRAINT "FK_817ea28e71e3b19acc258dd7dcd"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_8adc1fd6cb0dad2fbfd945954d"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."cronTrigger" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" DROP CONSTRAINT "FK_9acc2804037a5c885633024368d"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_960465af116edf9ac501bfb3db"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."databaseEventTrigger" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" DROP CONSTRAINT "FK_6edf47a8bfe17a5811998dc7162"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e9c53b9ac5035d3202a8737020"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."routeTrigger" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" DROP CONSTRAINT "FK_62cbd26626ff76df897181c7994"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_5b43e65e322d516c9307bed97a"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" DROP CONSTRAINT "FK_46f69b93b58666bb388c5c7785a"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_e6398c21e6bb31b525272fac84"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" DROP CONSTRAINT "FK_fb84d310b4cfe5916ced6fc3e2a"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_2a33a0e7e44c393ca7bb578dae"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" DROP CONSTRAINT "FK_5e7f19b88c0864db19e2bad0fc5"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_256fabec226411154baba649df"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" DROP CONSTRAINT "FK_4493447c2e4029aa26cabf30460"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_3763c4e8f942ff1e24040a13a9"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_0082568653b80c15903c5a2ba9" ON "core"."roleTarget" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e46f3e01227f1c8ee0c8041821" ON "core"."rowLevelPermissionPredicate" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_a14b5665091e86d461fb585924" ON "core"."rowLevelPermissionPredicateGroup" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e6ed40a61e4584e98584019a47" ON "core"."viewFilterGroup" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_38232fc0c6567ed029c2b1a12c" ON "core"."viewSort" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_8adc1fd6cb0dad2fbfd945954d" ON "core"."cronTrigger" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_960465af116edf9ac501bfb3db" ON "core"."databaseEventTrigger" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e9c53b9ac5035d3202a8737020" ON "core"."routeTrigger" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_5b43e65e322d516c9307bed97a" ON "core"."serverlessFunction" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_e6398c21e6bb31b525272fac84" ON "core"."skill" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_2a33a0e7e44c393ca7bb578dae" ON "core"."pageLayoutWidget" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_256fabec226411154baba649df" ON "core"."pageLayout" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_3763c4e8f942ff1e24040a13a9" ON "core"."pageLayoutTab" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."roleTarget" ADD CONSTRAINT "FK_b1db027b64f44029389ace305ac" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicate" ADD CONSTRAINT "FK_23b36d07d363f81200654fa1334" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."rowLevelPermissionPredicateGroup" ADD CONSTRAINT "FK_1e82563accb67114f65a3993b86" 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"."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"."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`, + ); + await queryRunner.query( + `ALTER TABLE "core"."serverlessFunction" ADD CONSTRAINT "FK_62cbd26626ff76df897181c7994" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."skill" ADD CONSTRAINT "FK_46f69b93b58666bb388c5c7785a" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutWidget" ADD CONSTRAINT "FK_fb84d310b4cfe5916ced6fc3e2a" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayout" ADD CONSTRAINT "FK_5e7f19b88c0864db19e2bad0fc5" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "core"."pageLayoutTab" ADD CONSTRAINT "FK_4493447c2e4029aa26cabf30460" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts index 487d6edc3d..3b4255cc29 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts @@ -14,7 +14,7 @@ import { DEFAULT_SMART_MODEL, ModelId, } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('agent') @Index('IDX_AGENT_ID_DELETED_AT', ['id', 'deletedAt']) @@ -23,7 +23,7 @@ import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/synca where: '"deletedAt" IS NULL', }) export class AgentEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts index 20b7d57f1f..44c26064b6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity.ts @@ -11,7 +11,7 @@ import { } from 'typeorm'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; export enum CommandMenuItemAvailabilityType { GLOBAL = 'GLOBAL', @@ -28,7 +28,7 @@ export enum CommandMenuItemAvailabilityType { 'availabilityObjectMetadataId', ]) export class CommandMenuItemEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') 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 c2353a5aa8..f39808e62c 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 @@ -30,7 +30,7 @@ import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permis import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('fieldMetadata') @Check( @@ -56,7 +56,7 @@ import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/synca export class FieldMetadataEntity< TFieldMetadataType extends FieldMetadataType = FieldMetadataType, > - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-to-create.util.ts index 392f136f2d..888c629020 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate-group/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group-to-create.util.ts @@ -11,9 +11,11 @@ export const fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermis createRowLevelPermissionPredicateGroupInput: rawCreateRowLevelPermissionPredicateGroupInput, workspaceId, + workspaceCustomApplicationId, }: { createRowLevelPermissionPredicateGroupInput: CreateRowLevelPermissionPredicateGroupInput; workspaceId: string; + workspaceCustomApplicationId: string; }): FlatRowLevelPermissionPredicateGroup => { const sanitizedInput = ( trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties as unknown as ( @@ -55,6 +57,6 @@ export const fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermis positionInRowLevelPermissionPredicateGroup: createRowLevelPermissionPredicateGroupInput.positionInRowLevelPermissionPredicateGroup ?? null, - applicationId: null, + applicationId: workspaceCustomApplicationId, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-to-create.util.ts index 91ea3d22bf..c608486b47 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate-to-create.util.ts @@ -11,9 +11,11 @@ export const fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionP createRowLevelPermissionPredicateInput: rawCreateRowLevelPermissionPredicateInput, workspaceId, + workspaceCustomApplicationId, }: { createRowLevelPermissionPredicateInput: CreateRowLevelPermissionPredicateInput; workspaceId: string; + workspaceCustomApplicationId: string; }): FlatRowLevelPermissionPredicate => { const sanitizedInput = ( trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties as unknown as ( @@ -67,6 +69,6 @@ export const fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionP workspaceMemberSubFieldName: createRowLevelPermissionPredicateInput.workspaceMemberSubFieldName ?? null, - applicationId: null, + applicationId: workspaceCustomApplicationId, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/front-component/entities/front-component.entity.ts b/packages/twenty-server/src/engine/metadata-modules/front-component/entities/front-component.entity.ts index c8f18f559b..aa13fa7bc0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/front-component/entities/front-component.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/front-component/entities/front-component.entity.ts @@ -6,11 +6,11 @@ import { UpdateDateColumn, } from 'typeorm'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('frontComponent') export class FrontComponentEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts index 3883e79afc..17e2a38e65 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.entity.ts @@ -15,7 +15,7 @@ import { import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Unique('IDX_INDEX_METADATA_NAME_WORKSPACE_ID_OBJECT_METADATA_ID_UNIQUE', [ 'name', @@ -28,7 +28,7 @@ import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/synca ]) @Entity('indexMetadata') export class IndexMetadataEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') 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 d48021ae34..c525a048e7 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 @@ -19,7 +19,7 @@ import { type ObjectStandardOverridesDTO } from 'src/engine/metadata-modules/obj 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 { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('objectMetadata') @Unique('IDX_OBJECT_METADATA_NAME_SINGULAR_WORKSPACE_ID_UNIQUE', [ @@ -32,7 +32,7 @@ import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/synca ]) @Index('IDX_OBJECT_METADATA_DATA_SOURCE_ID', ['dataSourceId']) export class ObjectMetadataEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') 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 f5a0669f53..7561620d2e 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 @@ -15,14 +15,11 @@ import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-fla import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity'; import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('role') @Unique('IDX_ROLE_LABEL_WORKSPACE_ID_UNIQUE', ['label', 'workspaceId']) -export class RoleEntity - extends SyncableEntityRequired - implements Required -{ +export class RoleEntity extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/row-level-permission.module.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/row-level-permission.module.ts index 7dfd70a66e..b53112bfb0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/row-level-permission.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/row-level-permission.module.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity'; @@ -24,6 +25,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace WorkspaceManyOrAllFlatEntityMapsCacheModule, WorkspaceMigrationModule, BillingModule, + ApplicationModule, ], providers: [ RowLevelPermissionPredicateService, diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service.ts index bb787f771b..d8ecf830fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate-group.service.ts @@ -7,6 +7,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; @@ -42,6 +43,7 @@ export class RowLevelPermissionPredicateGroupService { @InjectRepository(RowLevelPermissionPredicateGroupEntity) private readonly rowLevelPermissionPredicateGroupRepository: Repository, private readonly configService: ConfigService, + private readonly applicationService: ApplicationService, ) {} async createOne({ @@ -52,12 +54,19 @@ export class RowLevelPermissionPredicateGroupService { workspaceId: string; }): Promise { await this.hasRowLevelPermissionFeatureOrThrow(workspaceId); + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); const flatGroupToCreate = fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroupToCreate( { createRowLevelPermissionPredicateGroupInput, workspaceId, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, }, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts index 66a2b3182d..1b09832b0a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts @@ -6,6 +6,7 @@ import { ConfigService } from '@nestjs/config'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; @@ -47,6 +48,7 @@ export class RowLevelPermissionPredicateService { private readonly workspaceCacheService: WorkspaceCacheService, private readonly billingService: BillingService, private readonly configService: ConfigService, + private readonly applicationService: ApplicationService, ) {} async createOne({ @@ -58,11 +60,18 @@ export class RowLevelPermissionPredicateService { }): Promise { await this.hasRowLevelPermissionFeatureOrThrow(workspaceId); + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); const flatPredicateToCreate = fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicateToCreate( { createRowLevelPermissionPredicateInput, workspaceId, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, }, ); @@ -322,6 +331,12 @@ export class RowLevelPermissionPredicateService { const { roleId, objectMetadataId, predicates, predicateGroups } = input; + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); const { flatRowLevelPermissionPredicateMaps, flatRowLevelPermissionPredicateGroupMaps, @@ -366,6 +381,7 @@ export class RowLevelPermissionPredicateService { objectMetadataId, workspaceId, flatRowLevelPermissionPredicateGroupMaps, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, }); const { predicatesToCreate, predicatesToUpdate, predicatesToDelete } = @@ -376,6 +392,7 @@ export class RowLevelPermissionPredicateService { objectMetadataId, workspaceId, flatRowLevelPermissionPredicateMaps, + workspaceCustomApplicationId: workspaceCustomFlatApplication.id, }); await this.runUpsertMigration({ @@ -435,6 +452,7 @@ export class RowLevelPermissionPredicateService { objectMetadataId, workspaceId, flatRowLevelPermissionPredicateGroupMaps, + workspaceCustomApplicationId, }: { existingGroups: FlatRowLevelPermissionPredicateGroup[]; inputGroups: RowLevelPermissionPredicateGroupInput[]; @@ -442,6 +460,7 @@ export class RowLevelPermissionPredicateService { objectMetadataId: string; workspaceId: string; flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + workspaceCustomApplicationId: string; }): { groupsToCreate: FlatRowLevelPermissionPredicateGroup[]; groupsToUpdate: FlatRowLevelPermissionPredicateGroup[]; @@ -488,7 +507,7 @@ export class RowLevelPermissionPredicateService { updatedAt: createdAt, deletedAt: null, universalIdentifier: groupId, - applicationId: null, + applicationId: workspaceCustomApplicationId, }); } } @@ -515,6 +534,7 @@ export class RowLevelPermissionPredicateService { objectMetadataId, workspaceId, flatRowLevelPermissionPredicateMaps, + workspaceCustomApplicationId, }: { existingPredicates: FlatRowLevelPermissionPredicate[]; inputPredicates: RowLevelPermissionPredicateInput[]; @@ -522,6 +542,7 @@ export class RowLevelPermissionPredicateService { objectMetadataId: string; workspaceId: string; flatRowLevelPermissionPredicateMaps: FlatEntityMaps; + workspaceCustomApplicationId: string; }): { predicatesToCreate: FlatRowLevelPermissionPredicate[]; predicatesToUpdate: FlatRowLevelPermissionPredicate[]; @@ -583,7 +604,7 @@ export class RowLevelPermissionPredicateService { updatedAt: createdAt, deletedAt: null, universalIdentifier: predicateId, - applicationId: null, + applicationId: workspaceCustomApplicationId, }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts index 199c7aae27..bd20b340ea 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/entities/view-field.entity.ts @@ -14,7 +14,7 @@ import { import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity({ name: 'viewField', schema: 'core' }) @Index('IDX_VIEW_FIELD_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) @@ -29,7 +29,7 @@ import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/synca }, ) export class ViewFieldEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts index 387ccedcc7..ae930e8bae 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/entities/view-filter.entity.ts @@ -16,14 +16,14 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; import { type ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity({ name: 'viewFilter', schema: 'core' }) @Index('IDX_VIEW_FILTER_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) @Index('IDX_VIEW_FILTER_VIEW_ID', ['viewId']) @Index('IDX_VIEW_FILTER_FIELD_METADATA_ID', ['fieldMetadataId']) export class ViewFilterEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') 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 5a7f51300a..824b73b3e9 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 @@ -12,13 +12,13 @@ import { } from 'typeorm'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity({ name: 'viewGroup', schema: 'core' }) @Index('IDX_VIEW_GROUP_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) @Index('IDX_VIEW_GROUP_VIEW_ID', ['viewId']) export class ViewGroupEntity - extends SyncableEntityRequired + extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/services/tests/view-sort.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/services/tests/view-sort.service.spec.ts index 3111c9e5ae..77155a103c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/services/tests/view-sort.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/services/tests/view-sort.service.spec.ts @@ -3,6 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { type Repository } from 'typeorm'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction'; import { @@ -18,6 +19,7 @@ import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage describe('ViewSortService', () => { let viewSortService: ViewSortService; let viewSortRepository: Repository; + let applicationService: ApplicationService; const mockViewSort = { id: 'view-sort-id', @@ -51,6 +53,12 @@ describe('ViewSortService', () => { flushGraphQLOperation: jest.fn(), }, }, + { + provide: ApplicationService, + useValue: { + findWorkspaceTwentyStandardAndCustomApplicationOrThrow: jest.fn(), + }, + }, ], }).compile(); @@ -58,6 +66,7 @@ describe('ViewSortService', () => { viewSortRepository = module.get>( getRepositoryToken(ViewSortEntity), ); + applicationService = module.get(ApplicationService); }); it('should be defined', () => { @@ -151,12 +160,26 @@ describe('ViewSortService', () => { }; it('should create a view sort successfully', async () => { + const mockApplicationId = 'application-id'; + + jest + .spyOn( + applicationService, + 'findWorkspaceTwentyStandardAndCustomApplicationOrThrow', + ) + .mockResolvedValue({ + workspaceCustomFlatApplication: { id: mockApplicationId }, + } as any); jest.spyOn(viewSortRepository, 'create').mockReturnValue(mockViewSort); jest.spyOn(viewSortRepository, 'save').mockResolvedValue(mockViewSort); const result = await viewSortService.create(validViewSortData); - expect(viewSortRepository.create).toHaveBeenCalledWith(validViewSortData); + expect(viewSortRepository.create).toHaveBeenCalledWith({ + ...validViewSortData, + universalIdentifier: expect.any(String), + applicationId: mockApplicationId, + }); expect(viewSortRepository.save).toHaveBeenCalledWith(mockViewSort); expect(result).toEqual(mockViewSort); }); @@ -182,6 +205,15 @@ describe('ViewSortService', () => { it('should throw exception when viewId is missing', async () => { const invalidData = { ...validViewSortData, viewId: undefined }; + jest + .spyOn( + applicationService, + 'findWorkspaceTwentyStandardAndCustomApplicationOrThrow', + ) + .mockResolvedValue({ + workspaceCustomFlatApplication: { id: 'application-id' }, + } as any); + await expect(viewSortService.create(invalidData)).rejects.toThrow( new ViewSortException( generateViewSortExceptionMessage( @@ -200,6 +232,15 @@ describe('ViewSortService', () => { it('should throw exception when fieldMetadataId is missing', async () => { const invalidData = { ...validViewSortData, fieldMetadataId: undefined }; + jest + .spyOn( + applicationService, + 'findWorkspaceTwentyStandardAndCustomApplicationOrThrow', + ) + .mockResolvedValue({ + workspaceCustomFlatApplication: { id: 'application-id' }, + } as any); + await expect(viewSortService.create(invalidData)).rejects.toThrow( new ViewSortException( generateViewSortExceptionMessage( diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/services/view-sort.service.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/services/view-sort.service.ts index 346206d2d5..fdd8589f51 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/services/view-sort.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/services/view-sort.service.ts @@ -3,7 +3,9 @@ import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; import { IsNull, Repository } from 'typeorm'; +import { v4 } from 'uuid'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; import { ViewSortException, @@ -15,12 +17,14 @@ import { import { FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION } from 'src/engine/metadata-modules/view/constants/find-all-core-views-graphql-operation.constant'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +// TODO migrate to v2 @Injectable() export class ViewSortService { constructor( @InjectRepository(ViewSortEntity) private readonly viewSortRepository: Repository, private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, + private readonly applicationService: ApplicationService, ) {} async findByWorkspaceId(workspaceId: string): Promise { @@ -78,6 +82,13 @@ export class ViewSortService { ); } + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId: viewSortData.workspaceId, + }, + ); + if (!isDefined(viewSortData.viewId)) { throw new ViewSortException( generateViewSortExceptionMessage( @@ -106,7 +117,11 @@ export class ViewSortService { ); } - const viewSort = this.viewSortRepository.create(viewSortData); + const viewSort = this.viewSortRepository.create({ + ...viewSortData, + universalIdentifier: v4(), + applicationId: workspaceCustomFlatApplication.id, + }); const savedViewSort = await this.viewSortRepository.save(viewSort); diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/view-sort.module.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/view-sort.module.ts index ac14f57bdb..96fd7fa04b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/view-sort.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/view-sort.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { ViewPermissionsModule } from 'src/engine/metadata-modules/view-permissions/view-permissions.module'; import { ViewSortController } from 'src/engine/metadata-modules/view-sort/controllers/view-sort.controller'; @@ -16,6 +17,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ PermissionsModule, WorkspaceCacheStorageModule, ViewPermissionsModule, + ApplicationModule, ], controllers: [ViewSortController], providers: [ViewSortService, ViewSortResolver], diff --git a/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts index 8dbee99bb6..57c4eb3280 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/entities/view.entity.ts @@ -27,7 +27,7 @@ import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum'; import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in'; import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; import { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visibility.enum'; -import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; +import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; // We could refactor this type to be dynamic to view type @Entity({ name: 'view', schema: 'core' }) @@ -46,10 +46,7 @@ import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/synca 'CHK_VIEW_CALENDAR_INTEGRITY', `("type" != 'CALENDAR' OR ("calendarLayout" IS NOT NULL AND "calendarFieldMetadataId" IS NOT NULL))`, ) -export class ViewEntity - extends SyncableEntityRequired - implements Required -{ +export class ViewEntity extends SyncableEntity implements Required { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity-required.interface.ts b/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity-required.interface.ts deleted file mode 100644 index 9eebe0b9a9..0000000000 --- a/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity-required.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Column, Index, JoinColumn, ManyToOne, type Relation } from 'typeorm'; - -import type { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; -import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity'; - -@Index(['workspaceId', 'universalIdentifier'], { - unique: true, -}) -export abstract class SyncableEntityRequired extends WorkspaceRelatedEntity { - @Column({ nullable: false, type: 'uuid' }) - universalIdentifier: string; - - @Column({ nullable: false, type: 'uuid' }) - applicationId: string; - - @ManyToOne('ApplicationEntity', { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'applicationId' }) - application: Relation; -} diff --git a/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity.interface.ts b/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity.interface.ts index 11d6a1fe71..e94c76dfa6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/types/syncable-entity.interface.ts @@ -7,16 +7,15 @@ import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/works unique: true, }) export abstract class SyncableEntity extends WorkspaceRelatedEntity { - @Column({ nullable: true, type: 'uuid' }) - // TODO should not be nullable + @Column({ nullable: false, type: 'uuid' }) universalIdentifier: string; - @Column({ nullable: true, type: 'uuid' }) - applicationId: string | null; + @Column({ nullable: false, type: 'uuid' }) + applicationId: string; @ManyToOne('ApplicationEntity', { onDelete: 'CASCADE', - nullable: true, + nullable: false, }) @JoinColumn({ name: 'applicationId' }) application: Relation; diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts index 1c0ac4f3fc..27c64cf804 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts @@ -258,7 +258,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { isTool: false, serverlessFunctionLayerId: 'layer-id', universalIdentifier: 'universal-id', - applicationId: null, + applicationId: 'application-id', cronTriggerIds: [], databaseEventTriggerIds: [], routeTriggerIds: [], @@ -337,7 +337,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { isTool: false, serverlessFunctionLayerId: 'layer-id', universalIdentifier: 'universal-id', - applicationId: null, + applicationId: 'application-id', cronTriggerIds: [], databaseEventTriggerIds: [], routeTriggerIds: [],