Migrate messageFolder parentFolderId from UUID to externalId (#19348)

This PR migrates `messageFolder`.`parentFolderId` from an internal
`UUID` reference to external provider id.
Eliminates unnecessary lookup and complexity

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
neo773
2026-04-06 14:42:44 +05:30
committed by GitHub
parent 0d44d7c6d7
commit 8d61bb9ae6
6 changed files with 117 additions and 21 deletions
@@ -0,0 +1,98 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
const UUID_REGEX =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@Command({
name: 'upgrade:1-21:migrate-message-folder-parent-id-to-external-id',
description:
'Migrate messageFolder parentFolderId from internal UUID references to external IDs',
})
export class MigrateMessageFolderParentIdToExternalIdCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
@InjectRepository(MessageFolderEntity)
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const folders = await this.messageFolderRepository.find({
where: { workspaceId },
});
const idToExternalIdMap = new Map<string, string>();
const existingExternalIds = new Set<string>();
for (const folder of folders) {
if (folder.externalId) {
idToExternalIdMap.set(folder.id, folder.externalId);
existingExternalIds.add(folder.externalId);
}
}
let migratedCount = 0;
for (const folder of folders) {
if (!folder.parentFolderId) {
continue;
}
if (!UUID_REGEX.test(folder.parentFolderId)) {
continue;
}
// Already points to a valid externalId — skip even if it looks like a UUID
if (existingExternalIds.has(folder.parentFolderId)) {
continue;
}
const parentExternalId = idToExternalIdMap.get(folder.parentFolderId);
if (!parentExternalId) {
this.logger.warn(
`Folder ${folder.id}: parent ${folder.parentFolderId} not found or has no externalId, setting to null`,
);
if (!isDryRun) {
await this.messageFolderRepository.update(folder.id, {
parentFolderId: null,
});
}
migratedCount++;
continue;
}
if (isDryRun) {
this.logger.log(
`[DRY RUN] Would update folder ${folder.id}: parentFolderId ${folder.parentFolderId} -> ${parentExternalId}`,
);
} else {
await this.messageFolderRepository.update(folder.id, {
parentFolderId: parentExternalId,
});
}
migratedCount++;
}
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Migrated ${migratedCount} folder(s) for workspace ${workspaceId}`,
);
}
}
@@ -8,6 +8,7 @@ import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/databas
import { DeduplicateEngineCommandsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-deduplicate-engine-commands.command';
import { DropWorkspaceMessagingFksCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-drop-workspace-messaging-fks.command';
import { FixSelectAllCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-fix-select-all-command-menu-items.command';
import { MigrateMessageFolderParentIdToExternalIdCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-migrate-message-folder-parent-id-to-external-id.command';
import { MigrateAiAgentTextToJsonResponseFormatCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-migrate-ai-agent-text-to-json-response-format.command';
import { UpdateEditLayoutCommandMenuItemLabelCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-update-edit-layout-command-menu-item-label.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
@@ -15,13 +16,18 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, DataSourceEntity]),
TypeOrmModule.forFeature([
WorkspaceEntity,
DataSourceEntity,
MessageFolderEntity,
]),
DataSourceModule,
WorkspaceCacheModule,
ApplicationModule,
@@ -39,6 +45,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
MigrateAiAgentTextToJsonResponseFormatCommand,
UpdateEditLayoutCommandMenuItemLabelCommand,
DropWorkspaceMessagingFksCommand,
MigrateMessageFolderParentIdToExternalIdCommand,
],
exports: [
AddGlobalKeyValuePairUniqueIndexCommand,
@@ -49,6 +56,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
MigrateAiAgentTextToJsonResponseFormatCommand,
UpdateEditLayoutCommandMenuItemLabelCommand,
DropWorkspaceMessagingFksCommand,
MigrateMessageFolderParentIdToExternalIdCommand,
],
})
export class V1_21_UpgradeVersionCommandModule {}
@@ -37,6 +37,7 @@ import { CoreEngineVersionService } from 'src/engine/core-engine-version/service
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
import { DropWorkspaceMessagingFksCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-drop-workspace-messaging-fks.command';
import { MigrateMessageFolderParentIdToExternalIdCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-migrate-message-folder-parent-id-to-external-id.command';
@Command({
name: 'upgrade',
@@ -80,6 +81,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
private readonly migrateAiAgentTextToJsonResponseFormatCommand: MigrateAiAgentTextToJsonResponseFormatCommand,
private readonly updateEditLayoutCommandMenuItemLabelCommand: UpdateEditLayoutCommandMenuItemLabelCommand,
private readonly dropWorkspaceMessagingFksCommand: DropWorkspaceMessagingFksCommand,
private readonly migrateMessageFolderParentIdToExternalIdCommand: MigrateMessageFolderParentIdToExternalIdCommand,
) {
super(
workspaceRepository,
@@ -120,6 +122,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.migrateAiAgentTextToJsonResponseFormatCommand,
this.updateEditLayoutCommandMenuItemLabelCommand,
this.dropWorkspaceMessagingFksCommand,
this.migrateMessageFolderParentIdToExternalIdCommand,
];
this.allCommands = {