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: [],