Compare commits

...

2 Commits

Author SHA1 Message Date
prastoin 2e893eed0d fix 2026-03-24 14:33:33 +01:00
prastoin b782ed4ca2 fix(server): negative number check and orphan favorite 2026-03-24 10:32:28 +01:00
5 changed files with 103 additions and 14 deletions
@@ -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<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(`Deleting orphan favorites for workspace ${workspaceId}`);
const { flatViewMaps } = await this.workspaceCacheService.getOrRecompute(
workspaceId,
['flatViewMaps'],
);
const favoriteRepository =
await this.twentyORMGlobalManager.getRepository<FavoriteWorkspaceEntity>(
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}`,
);
}
}
@@ -284,7 +284,7 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
folderId: null,
folderUniversalIdentifier: null,
name: favoriteFolder.name,
position: favoriteFolder.position,
position: Math.round(favoriteFolder.position),
workspaceId,
applicationId: workspaceCustomApplicationId,
applicationUniversalIdentifier:
@@ -316,7 +316,7 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
folderId: null,
folderUniversalIdentifier: null,
name: favoriteFolder.name,
position: favoriteFolder.position,
position: Math.round(favoriteFolder.position),
workspaceId,
applicationId: workspaceCustomApplicationId,
applicationUniversalIdentifier:
@@ -521,7 +521,7 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
folderId,
folderUniversalIdentifier: folderId,
name: null,
position: favorite.position,
position: Math.round(favorite.position),
workspaceId,
applicationId,
applicationUniversalIdentifier,
@@ -566,7 +566,7 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
folderId,
folderUniversalIdentifier: folderId,
name: null,
position: favorite.position,
position: Math.round(favorite.position),
workspaceId,
applicationId: workspaceCustomApplicationId,
applicationUniversalIdentifier:
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillApplicationPackageFilesCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-backfill-application-package-files.command';
import { DeleteFileRecordsAndUpdateTableCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-delete-all-files-and-update-table.command';
import { DeleteOrphanFavoritesCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-delete-orphan-favorites.command';
import { FixMorphRelationFieldNamesCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-fix-morph-relation-field-names.command';
import { IdentifyWebhookMetadataCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-identify-webhook-metadata.command';
import { MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-make-webhook-universal-identifier-and-application-id-not-nullable-migration.command';
@@ -68,6 +69,7 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas
GlobalWorkspaceDataSourceModule,
],
providers: [
DeleteOrphanFavoritesCommand,
FixMorphRelationFieldNamesCommand,
MigrateAttachmentToMorphRelationsCommand,
MigrateFavoritesToNavigationMenuItemsCommand,
@@ -82,6 +84,7 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas
BackfillApplicationPackageFilesCommand,
],
exports: [
DeleteOrphanFavoritesCommand,
FixMorphRelationFieldNamesCommand,
MigrateAttachmentToMorphRelationsCommand,
MigrateFavoritesToNavigationMenuItemsCommand,
@@ -15,6 +15,7 @@ import { FixMorphRelationFieldNamesCommand } from 'src/database/commands/upgrade
import { IdentifyWebhookMetadataCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-identify-webhook-metadata.command';
import { MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-make-webhook-universal-identifier-and-application-id-not-nullable-migration.command';
import { MigrateAttachmentToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command';
import { DeleteOrphanFavoritesCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-delete-orphan-favorites.command';
import { MigrateFavoritesToNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-favorites-to-navigation-menu-items.command';
import { MigrateNoteTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-note-target-to-morph-relations.command';
import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-task-target-to-morph-relations.command';
@@ -42,6 +43,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly backfillApplicationPackageFilesCommand: BackfillApplicationPackageFilesCommand,
protected readonly deleteFileRecordsAndUpdateTableCommand: DeleteFileRecordsAndUpdateTableCommand,
protected readonly migrateAttachmentToMorphRelationsCommand: MigrateAttachmentToMorphRelationsCommand,
protected readonly deleteOrphanFavoritesCommand: DeleteOrphanFavoritesCommand,
protected readonly migrateFavoritesToNavigationMenuItemsCommand: MigrateFavoritesToNavigationMenuItemsCommand,
protected readonly migrateNoteTargetToMorphRelationsCommand: MigrateNoteTargetToMorphRelationsCommand,
protected readonly migrateTaskTargetToMorphRelationsCommand: MigrateTaskTargetToMorphRelationsCommand,
@@ -62,6 +64,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
const commands_1170: VersionCommands = [
this.migrateAttachmentToMorphRelationsCommand,
this.deleteOrphanFavoritesCommand,
this.migrateFavoritesToNavigationMenuItemsCommand,
this.migrateNoteTargetToMorphRelationsCommand,
this.migrateTaskTargetToMorphRelationsCommand,
@@ -166,13 +166,12 @@ export class FlatNavigationMenuItemValidatorService {
if (
isDefined(flatNavigationMenuItem.position) &&
(!Number.isInteger(flatNavigationMenuItem.position) ||
flatNavigationMenuItem.position < 0)
!Number.isFinite(flatNavigationMenuItem.position)
) {
validationResult.errors.push({
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
message: t`Position must be a non-negative integer`,
userFriendlyMessage: msg`Position must be a non-negative integer`,
message: t`Position must be a finite number`,
userFriendlyMessage: msg`Position must be a finite number`,
});
}
@@ -293,14 +292,11 @@ export class FlatNavigationMenuItemValidatorService {
const positionUpdate = flatEntityUpdate.position;
if (
isDefined(positionUpdate) &&
(!Number.isInteger(positionUpdate) || positionUpdate < 0)
) {
if (isDefined(positionUpdate) && !Number.isFinite(positionUpdate)) {
validationResult.errors.push({
code: NavigationMenuItemExceptionCode.INVALID_NAVIGATION_MENU_ITEM_INPUT,
message: t`Position must be a non-negative integer`,
userFriendlyMessage: msg`Position must be a non-negative integer`,
message: t`Position must be a finite number`,
userFriendlyMessage: msg`Position must be a finite number`,
});
}