From ecb7270a7b6d69de0c5082a389826afea2db1f0c Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:00:39 +0530 Subject: [PATCH] fix: delete orphan favorites before 1.18 migration (#18219) --- .../1-18-delete-orphan-favorites.command.ts | 87 +++++++++++++++++++ .../1-18-upgrade-version-command.module.ts | 3 + .../upgrade.command.ts | 3 + 3 files changed, 93 insertions(+) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-delete-orphan-favorites.command.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-delete-orphan-favorites.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-delete-orphan-favorites.command.ts new file mode 100644 index 0000000000..37557b6773 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-delete-orphan-favorites.command.ts @@ -0,0 +1,87 @@ +import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; +import { IsNull, Not, 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 { 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'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; + +@Command({ + name: 'upgrade:1-18:delete-orphan-favorites', + description: + 'Delete favorites whose viewId does not exist in workspace view metadata (fixes upgrade failure)', +}) +export class DeleteOrphanFavoritesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + protected readonly logger = new Logger(DeleteOrphanFavoritesCommand.name); + + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + private readonly workspaceCacheService: WorkspaceCacheService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log(`Deleting orphan favorites for workspace ${workspaceId}`); + + const { flatViewMaps } = await this.workspaceCacheService.getOrRecompute( + workspaceId, + ['flatViewMaps'], + ); + + const favoriteRepository = + await this.twentyORMGlobalManager.getRepository( + workspaceId, + 'favorite', + { shouldBypassPermissionChecks: true }, + ); + + const favoritesWithViewId = await favoriteRepository.find({ + where: { + deletedAt: IsNull(), + viewId: Not(IsNull()), + }, + select: { id: true, viewId: true }, + }); + + const orphanFavoriteIds = favoritesWithViewId + .filter( + (favorite) => + !isDefined(flatViewMaps.universalIdentifierById[favorite.viewId]), + ) + .map((favorite) => favorite.id); + + if (orphanFavoriteIds.length === 0) { + this.logger.log(`No orphan favorites found for workspace ${workspaceId}`); + + return; + } + + if (options.dryRun) { + this.logger.log( + `[DRY RUN] Would delete ${orphanFavoriteIds.length} orphan favorite(s) for workspace ${workspaceId}`, + ); + + return; + } + + await favoriteRepository.delete(orphanFavoriteIds); + + this.logger.log( + `Deleted ${orphanFavoriteIds.length} orphan favorite(s) for workspace ${workspaceId}`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-upgrade-version-command.module.ts index 5c76a9be8c..3a812f931c 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-18/1-18-upgrade-version-command.module.ts @@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { BackfillFileSizeAndMimeTypeCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-file-size-and-mime-type.command'; import { BackfillMessageChannelThrottleRetryAfterCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-message-channel-throttle-retry-after.command'; import { BackfillStandardViewsAndFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-standard-views-and-field-metadata.command'; +import { DeleteOrphanFavoritesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-delete-orphan-favorites.command'; import { MigrateActivityRichTextAttachmentFileIdsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-activity-rich-text-attachment-file-ids.command'; import { MigrateAttachmentFilesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-attachment-files.command'; import { MigrateFavoritesToNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-favorites-to-navigation-menu-items.command'; @@ -55,6 +56,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta SecureHttpClientModule, ], providers: [ + DeleteOrphanFavoritesCommand, MigratePersonAvatarFilesCommand, MigrateFavoritesToNavigationMenuItemsCommand, MigrateAttachmentFilesCommand, @@ -66,6 +68,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta MigrateWorkflowSendEmailAttachmentsCommand, ], exports: [ + DeleteOrphanFavoritesCommand, MigratePersonAvatarFilesCommand, MigrateFavoritesToNavigationMenuItemsCommand, MigrateAttachmentFilesCommand, 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 9cfbfff8af..7857efac8f 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 @@ -20,6 +20,7 @@ import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/ import { BackfillFileSizeAndMimeTypeCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-file-size-and-mime-type.command'; import { BackfillMessageChannelThrottleRetryAfterCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-message-channel-throttle-retry-after.command'; import { BackfillStandardViewsAndFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-standard-views-and-field-metadata.command'; +import { DeleteOrphanFavoritesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-delete-orphan-favorites.command'; import { MigrateActivityRichTextAttachmentFileIdsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-activity-rich-text-attachment-file-ids.command'; import { MigrateAttachmentFilesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-attachment-files.command'; import { MigrateFavoritesToNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-favorites-to-navigation-menu-items.command'; @@ -61,6 +62,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly fixMorphRelationFieldNamesCommand: FixMorphRelationFieldNamesCommand, // 1.18 Commands + protected readonly deleteOrphanFavoritesCommand: DeleteOrphanFavoritesCommand, protected readonly migrateFavoritesToNavigationMenuItemsCommand: MigrateFavoritesToNavigationMenuItemsCommand, protected readonly migratePersonAvatarFilesCommand: MigratePersonAvatarFilesCommand, protected readonly backfillFileSizeAndMimeTypeCommand: BackfillFileSizeAndMimeTypeCommand, @@ -101,6 +103,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { ]; const commands_1180: VersionCommands = [ + this.deleteOrphanFavoritesCommand, this.migrateFavoritesToNavigationMenuItemsCommand, this.migratePersonAvatarFilesCommand, this.migrateAttachmentFilesCommand,