diff --git a/packages/twenty-server/src/database/commands/base.command.ts b/packages/twenty-server/src/database/commands/base.command.ts index 6715b5c293bd..419a6f114b2e 100644 --- a/packages/twenty-server/src/database/commands/base.command.ts +++ b/packages/twenty-server/src/database/commands/base.command.ts @@ -6,6 +6,7 @@ import { CommandRunner, Option } from 'nest-commander'; export type BaseCommandOptions = { workspaceId?: string; dryRun?: boolean; + verbose?: boolean; }; export abstract class BaseCommandRunner extends CommandRunner { @@ -25,6 +26,14 @@ export abstract class BaseCommandRunner extends CommandRunner { return true; } + @Option({ + flags: '--verbose', + description: 'Verbose output', + }) + parseVerbose() { + return true; + } + override async run( passedParams: string[], options: BaseCommandOptions, diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts new file mode 100644 index 000000000000..790d829a9041 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command.ts @@ -0,0 +1,95 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { IsNull, Repository } from 'typeorm'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +interface DeleteViewFieldsWithoutViewsCommandOptions + extends ActiveWorkspacesCommandOptions {} + +@Command({ + name: 'upgrade-0.33:delete-view-fields-without-views', + description: 'Delete ViewFields that do not have a View', +}) +export class DeleteViewFieldsWithoutViewsCommand extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + options: DeleteViewFieldsWithoutViewsCommandOptions, + workspaceIds: string[], + ): Promise { + this.logger.log( + 'Running command to delete ViewFields that do not have a View', + ); + + for (const workspaceId of workspaceIds) { + this.logger.log(`Running command for workspace ${workspaceId}`); + + try { + await this.deleteViewFieldsWithoutViewsForWorkspace( + workspaceId, + options, + ); + } catch (error) { + this.logger.log( + chalk.red( + `Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`, + ), + ); + continue; + } finally { + this.logger.log( + chalk.green(`Finished running command for workspace ${workspaceId}.`), + ); + } + } + + this.logger.log(chalk.green(`Command completed!`)); + } + + private async deleteViewFieldsWithoutViewsForWorkspace( + workspaceId: string, + options: DeleteViewFieldsWithoutViewsCommandOptions, + ): Promise { + const viewFieldRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'viewField', + false, + ); + + const viewFieldsWithoutViews = await viewFieldRepository.find({ + where: { + viewId: IsNull(), + }, + }); + + const viewFieldIds = viewFieldsWithoutViews.map((vf) => vf.id); + + if (!options.dryRun && viewFieldIds.length > 0) { + await viewFieldRepository.delete(viewFieldIds); + } + + if (options.verbose) { + this.logger.log( + chalk.yellow( + `Deleted ${viewFieldsWithoutViews.length} ViewFields that do not have a View`, + ), + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts index f85573e8a69c..a5a0acb850cb 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command.ts @@ -17,7 +17,6 @@ interface EnforceUniqueConstraintsCommandOptions company?: boolean; viewField?: boolean; viewSort?: boolean; - verbose?: boolean; } @Command({ @@ -42,14 +41,6 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn return true; } - @Option({ - flags: '--verbose', - description: 'Verbose output', - }) - parseVerbose() { - return true; - } - @Option({ flags: '--company', description: 'Enforce unique constraints on company domainName', @@ -250,9 +241,16 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn .getRawMany(); for (const duplicate of duplicates) { - const { fieldMetadataId, viewId } = duplicate; + const { + viewField_fieldMetadataId: fieldMetadataId, + viewField_viewId: viewId, + } = duplicate; const viewFields = await viewFieldRepository.find({ - where: { fieldMetadataId, viewId, deletedAt: IsNull() }, + where: { + fieldMetadataId, + viewId, + deletedAt: IsNull(), + }, order: { createdAt: 'DESC' }, }); @@ -292,9 +290,16 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn .getRawMany(); for (const duplicate of duplicates) { - const { fieldMetadataId, viewId } = duplicate; + const { + viewSort_fieldMetadataId: fieldMetadataId, + viewSort_viewId: viewId, + } = duplicate; const viewSorts = await viewSortRepository.find({ - where: { fieldMetadataId, viewId, deletedAt: IsNull() }, + where: { + fieldMetadataId, + viewId, + deletedAt: IsNull(), + }, order: { createdAt: 'DESC' }, }); diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts index 9089d80adbf1..972d9a582bad 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command.ts @@ -4,6 +4,7 @@ import { Command } from 'nest-commander'; import { Repository } from 'typeorm'; import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; +import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command'; import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command'; import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -23,6 +24,7 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner { protected readonly workspaceRepository: Repository, private readonly updateRichTextSearchVectorCommand: UpdateRichTextSearchVectorCommand, private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand, + private readonly deleteViewFieldsWithoutViewsCommand: DeleteViewFieldsWithoutViewsCommand, private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, ) { super(workspaceRepository); @@ -33,6 +35,11 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner { options: UpdateTo0_33CommandOptions, workspaceIds: string[], ): Promise { + await this.deleteViewFieldsWithoutViewsCommand.executeActiveWorkspacesCommand( + passedParam, + options, + workspaceIds, + ); await this.enforceUniqueConstraintsCommand.executeActiveWorkspacesCommand( passedParam, { diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts index 8707a7549084..cd7c1f7c2c39 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-33/0-33-upgrade-version.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command'; import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command'; import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression'; import { UpgradeTo0_33Command } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command'; @@ -26,6 +27,7 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage UpgradeTo0_33Command, UpdateRichTextSearchVectorCommand, EnforceUniqueConstraintsCommand, + DeleteViewFieldsWithoutViewsCommand, ], }) export class UpgradeTo0_33CommandModule {} diff --git a/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts b/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts index dc2904587944..0411d1ccee7a 100644 --- a/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts +++ b/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts @@ -2,7 +2,10 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/i import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; @@ -53,6 +56,7 @@ export class FavoriteFolderWorkspaceEntity extends BaseWorkspaceEntity { icon: 'IconHeart', inverseSideFieldKey: 'favoriteFolder', inverseSideTarget: () => FavoriteWorkspaceEntity, + onDelete: RelationOnDeleteAction.SET_NULL, }) favorites: Relation; } diff --git a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts index 44e2f331e8ab..9de31a8f0204 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view-field.workspace-entity.ts @@ -1,3 +1,5 @@ +import { Relation } from 'typeorm'; + import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; @@ -5,7 +7,6 @@ import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-enti import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator'; import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; @@ -77,9 +78,8 @@ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity { inverseSideTarget: () => ViewWorkspaceEntity, inverseSideFieldKey: 'viewFields', }) - @WorkspaceIsNullable() - view?: ViewWorkspaceEntity | null; + view: Relation; @WorkspaceJoinColumn('view') - viewId: string | null; + viewId: string; } diff --git a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts index 72d90b4dabfd..67af0c9fa215 100644 --- a/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts +++ b/packages/twenty-server/src/modules/view/standard-objects/view.workspace-entity.ts @@ -111,7 +111,7 @@ export class ViewWorkspaceEntity extends BaseWorkspaceEntity { description: 'View Fields', icon: 'IconTag', inverseSideTarget: () => ViewFieldWorkspaceEntity, - onDelete: RelationOnDeleteAction.SET_NULL, + onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsNullable() viewFields: Relation;