diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command.ts index 025b3a71d2..c0d1d8cd47 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command.ts @@ -216,17 +216,17 @@ export class IdentifyFieldMetadataCommand extends WorkspacesMigrationCommandRunn const relatedMetadataNames = getMetadataRelatedMetadataNames('fieldMetadata'); - const cacheKeysToInvalidate = relatedMetadataNames.map( + const relatedCacheKeysToInvalidate = relatedMetadataNames.map( getMetadataFlatEntityMapsKey, ); this.logger.log( - `Invalidating caches: ${cacheKeysToInvalidate.join(' ')}`, - ); - await this.workspaceCacheService.invalidateAndRecompute( - workspaceId, - cacheKeysToInvalidate, + `Invalidating caches: flatFieldMetadataMaps ${relatedCacheKeysToInvalidate.join(' ')}`, ); + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + 'flatFieldMetadataMaps', + ...relatedCacheKeysToInvalidate, + ]); this.logger.log( `Applied ${totalUpdates} field metadata update(s) for workspace ${workspaceId}`, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command.ts index 1bcf8503fb..c95ff4752c 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command.ts @@ -208,17 +208,17 @@ export class IdentifyObjectMetadataCommand extends WorkspacesMigrationCommandRun const relatedMetadataNames = getMetadataRelatedMetadataNames('objectMetadata'); - const cacheKeysToInvalidate = relatedMetadataNames.map( + const relatedCacheKeysToInvalidate = relatedMetadataNames.map( getMetadataFlatEntityMapsKey, ); this.logger.log( - `Invalidating caches: ${cacheKeysToInvalidate.join(' ')}`, - ); - await this.workspaceCacheService.invalidateAndRecompute( - workspaceId, - cacheKeysToInvalidate, + `Invalidating caches: flatObjectMetadataMaps ${relatedCacheKeysToInvalidate.join(' ')}`, ); + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + 'flatObjectMetadataMaps', + ...relatedCacheKeysToInvalidate, + ]); this.logger.log( `Applied ${totalUpdates} object metadata update(s) for workspace ${workspaceId}`, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command.ts new file mode 100644 index 0000000000..869328f889 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command.ts @@ -0,0 +1,378 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; +import { IsNull, Repository } from 'typeorm'; +import { v4 } from 'uuid'; + +import { + RunOnWorkspaceArgs, + WorkspacesMigrationCommandRunner, +} from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { computeFormattedViewName } from 'src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util'; +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 { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +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 { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.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 { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant'; + +type CustomViewFieldMetadata = { + viewFieldEntity: ViewFieldEntity; + fromStandard: boolean; +}; + +type StandardViewFieldMetadata = { + viewFieldEntity: ViewFieldEntity; + universalIdentifier: string; +}; + +type AllWarnings = + | 'standard_object_has_no_standard_views' + | 'unknown_view' + | 'unknown_standard_view_field'; + +type ViewFieldMetadataWarning = { + viewFieldEntity: ViewFieldEntity; + warning: AllWarnings; + objectNameSingular?: string; + viewName?: string; + fieldName?: string; +}; + +type AllExceptions = + | 'existing_universal_id_mismatch' + | 'view_not_found' + | 'object_not_found' + | 'field_not_found'; + +type ViewFieldMetadataException = { + viewFieldEntity: ViewFieldEntity; + exception: AllExceptions; + objectNameSingular?: string; + viewName?: string; + fieldName?: string; +}; + +@Command({ + name: 'upgrade:1-16:identify-view-field-metadata', + description: 'Identify standard view field metadata', +}) +export class IdentifyViewFieldMetadataCommand extends WorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + @InjectRepository(ViewFieldEntity) + private readonly viewFieldRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + protected readonly applicationService: ApplicationService, + protected readonly workspaceCacheService: WorkspaceCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService, [ + WorkspaceActivationStatus.ACTIVE, + WorkspaceActivationStatus.SUSPENDED, + WorkspaceActivationStatus.ONGOING_CREATION, + ]); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running identify standard view field metadata for workspace ${workspaceId}`, + ); + + const { twentyStandardFlatApplication, workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const allViewFieldEntities = await this.viewFieldRepository.find({ + select: { + id: true, + universalIdentifier: true, + applicationId: true, + viewId: true, + fieldMetadataId: true, + }, + where: { + workspaceId, + applicationId: IsNull(), + }, + }); + + const { flatObjectMetadataMaps, flatFieldMetadataMaps, flatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: [ + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + 'flatViewMaps', + ], + }, + ); + + const customViewFieldMetadataEntities: CustomViewFieldMetadata[] = []; + const standardViewFieldMetadataEntities: StandardViewFieldMetadata[] = []; + const warnings: ViewFieldMetadataWarning[] = []; + const exceptions: ViewFieldMetadataException[] = []; + + for (const viewFieldEntity of allViewFieldEntities) { + const flatView = flatViewMaps.byId[viewFieldEntity.viewId]; + + if (!isDefined(flatView)) { + exceptions.push({ + viewFieldEntity, + exception: 'view_not_found', + }); + continue; + } + + if (flatView.isCustom) { + customViewFieldMetadataEntities.push({ + viewFieldEntity, + fromStandard: false, + }); + continue; + } + + const flatObjectMetadata = + flatObjectMetadataMaps.byId[flatView.objectMetadataId]; + + if (!isDefined(flatObjectMetadata)) { + exceptions.push({ + viewFieldEntity, + exception: 'object_not_found', + viewName: flatView.name, + }); + continue; + } + + const flatFieldMetadata = + flatFieldMetadataMaps.byId[viewFieldEntity.fieldMetadataId]; + + if (!isDefined(flatFieldMetadata)) { + exceptions.push({ + viewFieldEntity, + exception: 'field_not_found', + objectNameSingular: flatObjectMetadata.nameSingular, + viewName: flatView.name, + }); + continue; + } + + if ( + flatObjectMetadata.applicationId !== twentyStandardFlatApplication.id + ) { + customViewFieldMetadataEntities.push({ + viewFieldEntity, + fromStandard: true, + }); + continue; + } + + const objectConfig = + STANDARD_OBJECTS[ + flatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECTS + ]; + + if (!isDefined(objectConfig)) { + exceptions.push({ + viewFieldEntity, + exception: 'object_not_found', + objectNameSingular: flatObjectMetadata.nameSingular, + viewName: flatView.name, + }); + continue; + } + + const objectViews = + 'views' in objectConfig + ? (objectConfig.views as Record< + string, + | { + universalIdentifier: string; + viewFields?: Record< + string, + { universalIdentifier: string } | undefined + >; + } + | undefined + >) + : null; + + if (!isDefined(objectViews)) { + warnings.push({ + viewFieldEntity, + warning: 'standard_object_has_no_standard_views', + objectNameSingular: flatObjectMetadata.nameSingular, + viewName: flatView.name, + }); + customViewFieldMetadataEntities.push({ + viewFieldEntity, + fromStandard: true, + }); + continue; + } + + const formattedViewName = computeFormattedViewName({ + flatObjectMetadata, + viewName: flatView.name, + }); + const viewConfig = objectViews[formattedViewName]; + + if (!isDefined(viewConfig) || !isDefined(viewConfig.viewFields)) { + warnings.push({ + viewFieldEntity, + warning: 'unknown_view', + objectNameSingular: flatObjectMetadata.nameSingular, + viewName: flatView.name, + }); + customViewFieldMetadataEntities.push({ + viewFieldEntity, + fromStandard: true, + }); + continue; + } + + const viewFieldConfig = viewConfig.viewFields[flatFieldMetadata.name]; + const universalIdentifier = viewFieldConfig?.universalIdentifier; + + if (!isDefined(universalIdentifier)) { + warnings.push({ + viewFieldEntity, + warning: 'unknown_standard_view_field', + objectNameSingular: flatObjectMetadata.nameSingular, + viewName: flatView.name, + fieldName: flatFieldMetadata.name, + }); + customViewFieldMetadataEntities.push({ + viewFieldEntity, + fromStandard: true, + }); + + continue; + } + + if ( + isDefined(viewFieldEntity.universalIdentifier) && + viewFieldEntity.universalIdentifier !== universalIdentifier + ) { + exceptions.push({ + viewFieldEntity, + exception: 'existing_universal_id_mismatch', + objectNameSingular: flatObjectMetadata.nameSingular, + viewName: flatView.name, + }); + continue; + } + + standardViewFieldMetadataEntities.push({ + viewFieldEntity, + universalIdentifier: + viewFieldEntity.universalIdentifier ?? universalIdentifier, + }); + } + + const totalUpdates = + customViewFieldMetadataEntities.length + + standardViewFieldMetadataEntities.length; + + if (warnings.length > 0) { + this.logger.warn( + `Found ${warnings.length} warning(s) while processing view field metadata for workspace ${workspaceId}. These view fields will become custom.`, + ); + + for (const { + viewFieldEntity, + warning, + objectNameSingular, + viewName, + fieldName, + } of warnings) { + this.logger.warn( + `Warning for view field on object "${objectNameSingular ?? 'unknown'}" in view "${viewName ?? 'unknown'}" for field ${fieldName ?? 'unknown'} (id=${viewFieldEntity.id}): ${warning}`, + ); + } + } + + if (exceptions.length > 0) { + this.logger.error( + `Found ${exceptions.length} exception(s) while processing view field metadata for workspace ${workspaceId}. No updates will be applied.`, + ); + + for (const { + viewFieldEntity, + exception, + objectNameSingular, + viewName, + fieldName, + } of exceptions) { + this.logger.error( + `Exception for view field on object "${objectNameSingular ?? 'unknown'}" in view "${viewName ?? 'unknown'}" for field ${fieldName ?? 'unknown'} (id=${viewFieldEntity.id}): ${exception}`, + ); + } + + throw new Error( + `Aborting migration for workspace ${workspaceId} due to ${exceptions.length} exception(s). See logs above for details.`, + ); + } + + this.logger.log( + `Successfully validated ${totalUpdates}/${allViewFieldEntities.length} view field metadata update(s) for workspace ${workspaceId} (${customViewFieldMetadataEntities.length} custom, ${standardViewFieldMetadataEntities.length} standard)`, + ); + + if (!options.dryRun) { + const customUpdates = customViewFieldMetadataEntities.map( + ({ viewFieldEntity }) => ({ + id: viewFieldEntity.id, + universalIdentifier: viewFieldEntity.universalIdentifier ?? v4(), + applicationId: workspaceCustomFlatApplication.id, + }), + ); + + const standardUpdates = standardViewFieldMetadataEntities.map( + ({ viewFieldEntity, universalIdentifier }) => ({ + id: viewFieldEntity.id, + universalIdentifier, + applicationId: twentyStandardFlatApplication.id, + }), + ); + + await this.viewFieldRepository.save([ + ...customUpdates, + ...standardUpdates, + ]); + + const relatedMetadataNames = getMetadataRelatedMetadataNames('viewField'); + const relatedCacheKeysToInvalidate = relatedMetadataNames.map( + getMetadataFlatEntityMapsKey, + ); + + this.logger.log( + `Invalidating caches: ${relatedCacheKeysToInvalidate.join(' ')}`, + ); + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + 'flatViewFieldMaps', + ...relatedCacheKeysToInvalidate, + ]); + + this.logger.log( + `Applied ${totalUpdates} view field metadata update(s) for workspace ${workspaceId}`, + ); + } else { + this.logger.log( + `Dry run: would apply ${totalUpdates} view field metadata update(s) for workspace ${workspaceId}`, + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command.ts new file mode 100644 index 0000000000..27d2215fa7 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command.ts @@ -0,0 +1,291 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; +import { IsNull, Repository } from 'typeorm'; +import { v4 } from 'uuid'; + +import { + RunOnWorkspaceArgs, + WorkspacesMigrationCommandRunner, +} from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { computeFormattedViewName } from 'src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util'; +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 { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +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 { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.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 { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant'; + +type CustomViewMetadata = { + viewEntity: ViewEntity; + fromStandard: boolean; +}; + +type StandardViewMetadata = { + viewEntity: ViewEntity; + universalIdentifier: string; +}; + +type AllWarnings = 'unknown_object'; + +type ViewMetadataWarning = { + viewEntity: ViewEntity; + warning: AllWarnings; + objectNameSingular?: string; +}; + +type AllExceptions = + | 'existing_universal_id_mismatch' + | 'not_found_object' + | 'unknown_standard_view_name'; + +type ViewMetadataException = { + viewEntity: ViewEntity; + exception: AllExceptions; + objectNameSingular?: string; +}; + +@Command({ + name: 'upgrade:1-16:identify-view-metadata', + description: 'Identify standard view metadata', +}) +export class IdentifyViewMetadataCommand extends WorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + @InjectRepository(ViewEntity) + private readonly viewRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + protected readonly applicationService: ApplicationService, + protected readonly workspaceCacheService: WorkspaceCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService, [ + WorkspaceActivationStatus.ACTIVE, + WorkspaceActivationStatus.SUSPENDED, + WorkspaceActivationStatus.ONGOING_CREATION, + ]); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running identify standard view metadata for workspace ${workspaceId}`, + ); + + const { twentyStandardFlatApplication, workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const allViewEntities = await this.viewRepository.find({ + select: { + id: true, + universalIdentifier: true, + applicationId: true, + name: true, + objectMetadataId: true, + isCustom: true, + }, + where: { + workspaceId, + applicationId: IsNull(), + }, + }); + + const { flatObjectMetadataMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatObjectMetadataMaps'], + }, + ); + + const customViewMetadataEntities: CustomViewMetadata[] = []; + const standardViewMetadataEntities: StandardViewMetadata[] = []; + const warnings: ViewMetadataWarning[] = []; + const exceptions: ViewMetadataException[] = []; + + for (const viewEntity of allViewEntities) { + // TODO double check that index view are not custom clearly not sure sure about that + if (viewEntity.isCustom) { + customViewMetadataEntities.push({ + viewEntity, + fromStandard: false, + }); + + continue; + } + + const flatObjectMetadata = + flatObjectMetadataMaps.byId[viewEntity.objectMetadataId]; + + if (!isDefined(flatObjectMetadata)) { + exceptions.push({ + viewEntity, + exception: 'not_found_object', + }); + continue; + } + + const objectConfig = + STANDARD_OBJECTS[ + flatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECTS + ]; + + if (!isDefined(objectConfig)) { + warnings.push({ + viewEntity, + warning: 'unknown_object', + objectNameSingular: flatObjectMetadata.nameSingular, + }); + customViewMetadataEntities.push({ + viewEntity, + fromStandard: true, + }); + continue; + } + + const objectViews = + 'views' in objectConfig + ? (objectConfig.views as Record< + string, + { universalIdentifier: string } | undefined + >) + : null; + + if (!isDefined(objectViews)) { + warnings.push({ + viewEntity, + warning: 'unknown_object', + objectNameSingular: flatObjectMetadata.nameSingular, + }); + customViewMetadataEntities.push({ + viewEntity, + fromStandard: true, + }); + continue; + } + + const formattedViewName = computeFormattedViewName({ + viewName: viewEntity.name, + flatObjectMetadata, + }); + + this.logger.log(formattedViewName); + const viewConfig = objectViews[formattedViewName]; + const universalIdentifier = viewConfig?.universalIdentifier; + + if (!isDefined(universalIdentifier)) { + exceptions.push({ + viewEntity, + exception: 'unknown_standard_view_name', + objectNameSingular: flatObjectMetadata.nameSingular, + }); + continue; + } + + if ( + isDefined(viewEntity.universalIdentifier) && + viewEntity.universalIdentifier !== universalIdentifier + ) { + exceptions.push({ + viewEntity, + exception: 'existing_universal_id_mismatch', + }); + continue; + } + + standardViewMetadataEntities.push({ + viewEntity, + universalIdentifier: + viewEntity.universalIdentifier ?? universalIdentifier, + }); + } + + const totalUpdates = + customViewMetadataEntities.length + standardViewMetadataEntities.length; + + if (warnings.length > 0) { + this.logger.warn( + `Found ${warnings.length} warning(s) while processing view metadata for workspace ${workspaceId}. These views will become custom.`, + ); + + for (const { viewEntity, warning, objectNameSingular } of warnings) { + this.logger.warn( + `Warning for view "${viewEntity.name}" on object "${objectNameSingular ?? 'unknown'}" (id=${viewEntity.id}): ${warning}`, + ); + } + } + + if (exceptions.length > 0) { + this.logger.error( + `Found ${exceptions.length} exception(s) while processing view metadata for workspace ${workspaceId}. No updates will be applied.`, + ); + + for (const { viewEntity, exception, objectNameSingular } of exceptions) { + this.logger.error( + `Exception for view "${viewEntity.name}" on object "${objectNameSingular ?? 'unknown'}" (id=${viewEntity.id}): ${exception}`, + ); + } + + throw new Error( + `Aborting migration for workspace ${workspaceId} due to ${exceptions.length} exception(s). See logs above for details.`, + ); + } + + this.logger.log( + `Successfully validated ${totalUpdates}/${allViewEntities.length} view metadata update(s) for workspace ${workspaceId} (${customViewMetadataEntities.length} custom, ${standardViewMetadataEntities.length} standard)`, + ); + + if (!options.dryRun) { + const customUpdates = customViewMetadataEntities.map( + ({ viewEntity }) => ({ + id: viewEntity.id, + universalIdentifier: viewEntity.universalIdentifier ?? v4(), + applicationId: workspaceCustomFlatApplication.id, + }), + ); + + const standardUpdates = standardViewMetadataEntities.map( + ({ viewEntity, universalIdentifier }) => ({ + id: viewEntity.id, + universalIdentifier, + applicationId: twentyStandardFlatApplication.id, + }), + ); + + await this.viewRepository.save([...customUpdates, ...standardUpdates]); + + const relatedMetadataNames = getMetadataRelatedMetadataNames('view'); + const relatedCacheKeysToInvalidate = relatedMetadataNames.map( + getMetadataFlatEntityMapsKey, + ); + + this.logger.log( + `Invalidating caches: ${relatedCacheKeysToInvalidate.join(' ')}`, + ); + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + 'flatViewMaps', + ...relatedCacheKeysToInvalidate, + ]); + + this.logger.log( + `Applied ${totalUpdates} view metadata update(s) for workspace ${workspaceId}`, + ); + } else { + this.logger.log( + `Dry run: would apply ${totalUpdates} view metadata update(s) for workspace ${workspaceId}`, + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-view-field-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-view-field-universal-identifier-and-application-id-not-nullable-migration.command.ts new file mode 100644 index 0000000000..320c13f41b --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-view-field-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 { makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.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-view-field-universal-identifier-and-application-id-not-nullable-migration', + description: + 'Make universalIdentifier and applicationId columns NOT NULL on viewField table', +}) +export class MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand 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 MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand', + ); + + return; + } + + if (options.dryRun) { + return; + } + + const queryRunner = this.coreDataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + await makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries( + queryRunner, + ); + + await queryRunner.commitTransaction(); + this.logger.log( + 'Successfully run MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand', + ); + this.hasRunOnce = true; + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error( + `Rolling back MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: ${error.message}`, + ); + } finally { + await queryRunner.release(); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-view-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-view-universal-identifier-and-application-id-not-nullable-migration.command.ts new file mode 100644 index 0000000000..948f443a17 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-make-view-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 { makeViewUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.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-view-universal-identifier-and-application-id-not-nullable-migration', + description: + 'Make universalIdentifier and applicationId columns NOT NULL on view table', +}) +export class MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand 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 MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand', + ); + + return; + } + + if (options.dryRun) { + return; + } + + const queryRunner = this.coreDataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + await makeViewUniversalIdentifierAndApplicationIdNotNullableQueries( + queryRunner, + ); + + await queryRunner.commitTransaction(); + this.logger.log( + 'Successfully run MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand', + ); + this.hasRunOnce = true; + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error( + `Rolling back MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: ${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 79dd43ddab..0c4794afee 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 @@ -5,15 +5,22 @@ import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgr import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command'; import { IdentifyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command'; import { IdentifyObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command'; +import { IdentifyViewFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command'; +import { IdentifyViewMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.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 { 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 { 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 { MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-universal-identifier-and-application-id-not-nullable-migration.command'; import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { GlobalWorkspaceDataSourceModule } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { TwentyStandardApplicationModule } from 'src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module'; @@ -25,6 +32,8 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace WorkspaceEntity, FieldMetadataEntity, ObjectMetadataEntity, + ViewEntity, + ViewFieldEntity, ]), DataSourceModule, WorkspaceCacheModule, @@ -33,6 +42,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace GlobalWorkspaceDataSourceModule, TwentyStandardApplicationModule, WorkspaceMigrationModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, ], providers: [ UpdateTaskOnDeleteActionCommand, @@ -40,8 +50,12 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace BackfillStandardPageLayoutsCommand, IdentifyFieldMetadataCommand, IdentifyObjectMetadataCommand, + IdentifyViewMetadataCommand, + IdentifyViewFieldMetadataCommand, MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ], exports: [ UpdateTaskOnDeleteActionCommand, @@ -49,8 +63,12 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace BackfillStandardPageLayoutsCommand, IdentifyFieldMetadataCommand, IdentifyObjectMetadataCommand, + IdentifyViewMetadataCommand, + IdentifyViewFieldMetadataCommand, MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ], }) export class V1_16_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util.ts new file mode 100644 index 0000000000..cd1e87208f --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util.ts @@ -0,0 +1,16 @@ +import { capitalize, uncapitalize } from 'twenty-shared/utils'; + +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; + +export const ALL_ENTITY_VIEW_NAME = 'All {objectLabelPlural}'; + +export const computeFormattedViewName = ({ + viewName, + flatObjectMetadata, +}: { + viewName: string; + flatObjectMetadata: FlatObjectMetadata; +}) => + viewName === ALL_ENTITY_VIEW_NAME + ? `all${capitalize(flatObjectMetadata.namePlural)}` + : uncapitalize(viewName.split(' ').map(capitalize).join('')); 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 125aa811b5..c1975956bd 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 @@ -26,8 +26,12 @@ import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgr import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command'; import { IdentifyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command'; import { IdentifyObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command'; +import { IdentifyViewFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command'; +import { IdentifyViewMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.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 { 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 { 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 { MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-universal-identifier-and-application-id-not-nullable-migration.command'; import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -73,8 +77,12 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly backfillStandardPageLayoutsCommand: BackfillStandardPageLayoutsCommand, protected readonly identifyFieldMetadataCommand: IdentifyFieldMetadataCommand, protected readonly identifyObjectMetadataCommand: IdentifyObjectMetadataCommand, + protected readonly identifyViewMetadataCommand: IdentifyViewMetadataCommand, + protected readonly identifyViewFieldMetadataCommand: IdentifyViewFieldMetadataCommand, protected readonly makeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, protected readonly makeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + protected readonly makeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + protected readonly makeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ) { super( workspaceRepository, @@ -114,10 +122,16 @@ export class UpgradeCommand extends UpgradeCommandRunner { this.backfillStandardPageLayoutsCommand, this.identifyFieldMetadataCommand, this.identifyObjectMetadataCommand, + this.identifyViewMetadataCommand, + this.identifyViewFieldMetadataCommand, this .makeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, this .makeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + this + .makeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + this + .makeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ]; this.allCommands = { diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.ts new file mode 100644 index 0000000000..d3b6d1bbd3 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.ts @@ -0,0 +1,63 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +import { makeViewUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.util'; + +export class MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271 + implements MigrationInterface +{ + name = 'MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271'; + + public async up(queryRunner: QueryRunner): Promise { + const savepointName = + 'sp_make_view_universal_identifier_and_application_id_not_nullable'; + + try { + await queryRunner.query(`SAVEPOINT ${savepointName}`); + + await makeViewUniversalIdentifierAndApplicationIdNotNullableQueries( + 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 MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271', + rollbackError, + ); + throw rollbackError; + } + + // eslint-disable-next-line no-console + console.error( + 'Swallowing MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271 error', + e, + ); + } + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."view" DROP CONSTRAINT "FK_348e25d584c7e51417f4e097941"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_552aa6908966e980099b3e5ebf"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_552aa6908966e980099b3e5ebf" ON "core"."view" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ADD CONSTRAINT "FK_348e25d584c7e51417f4e097941" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.ts new file mode 100644 index 0000000000..689994bb52 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.ts @@ -0,0 +1,64 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +import { makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.util'; + +export class MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272 + implements MigrationInterface +{ + name = + 'MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272'; + + public async up(queryRunner: QueryRunner): Promise { + const savepointName = + 'sp_make_view_field_universal_identifier_and_application_id_not_nullable'; + + try { + await queryRunner.query(`SAVEPOINT ${savepointName}`); + + await makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries( + 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 MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272', + rollbackError, + ); + throw rollbackError; + } + + // eslint-disable-next-line no-console + console.error( + 'Swallowing MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272 error', + e, + ); + } + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_b560ea62a958deff0c6059caa45"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_b86af4ea24cae518dee8eae996"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ALTER COLUMN "applicationId" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ALTER COLUMN "universalIdentifier" DROP NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_b86af4ea24cae518dee8eae996" ON "core"."viewField" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_b560ea62a958deff0c6059caa45" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.util.ts b/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.util.ts new file mode 100644 index 0000000000..e3b78be471 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.util.ts @@ -0,0 +1,23 @@ +import { type QueryRunner } from 'typeorm'; + +export const makeViewUniversalIdentifierAndApplicationIdNotNullableQueries = + async (queryRunner: QueryRunner): Promise => { + await queryRunner.query( + `ALTER TABLE "core"."view" DROP CONSTRAINT "FK_348e25d584c7e51417f4e097941"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_552aa6908966e980099b3e5ebf"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_552aa6908966e980099b3e5ebf" ON "core"."view" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."view" ADD CONSTRAINT "FK_348e25d584c7e51417f4e097941" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + }; diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.util.ts b/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.util.ts new file mode 100644 index 0000000000..ff317b8246 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.util.ts @@ -0,0 +1,23 @@ +import { type QueryRunner } from 'typeorm'; + +export const makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries = + async (queryRunner: QueryRunner): Promise => { + await queryRunner.query( + `ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_b560ea62a958deff0c6059caa45"`, + ); + await queryRunner.query( + `DROP INDEX "core"."IDX_b86af4ea24cae518dee8eae996"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ALTER COLUMN "universalIdentifier" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ALTER COLUMN "applicationId" SET NOT NULL`, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_b86af4ea24cae518dee8eae996" ON "core"."viewField" ("workspaceId", "universalIdentifier") `, + ); + await queryRunner.query( + `ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_b560ea62a958deff0c6059caa45" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + }; 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 314ca959c4..ac2feaec88 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 { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; +import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; @Entity({ name: 'viewField', schema: 'core' }) @Index('IDX_VIEW_FIELD_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId']) @@ -30,7 +30,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti }, ) export class ViewFieldEntity - extends SyncableEntity + extends SyncableEntityRequired implements Required { @PrimaryGeneratedColumn('uuid') 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 feaf6a9dc7..17f048cc64 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 { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; +import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface'; // We could refactor this type to be dynamic to view type @Entity({ name: 'view', schema: 'core' }) @@ -40,7 +40,10 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti 'CHK_VIEW_CALENDAR_INTEGRITY', `("type" != 'CALENDAR' OR ("calendarLayout" IS NOT NULL AND "calendarFieldMetadataId" IS NOT NULL))`, ) -export class ViewEntity extends SyncableEntity implements Required { +export class ViewEntity + extends SyncableEntityRequired + implements Required +{ @PrimaryGeneratedColumn('uuid') id: string;