Add messaging upgrade command 1 21 (#19389)
``` [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [WorkspaceIteratorService] Running on workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 1/2 [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 5 connected accounts for workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 6 message channels for workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 6 calendar channels for workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 5 message folders for workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [WorkspaceIteratorService] Running on workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db 2/2 [Nest] 50057 - 04/07/2026, 4:14:10 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 5 connected accounts for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db [Nest] 50057 - 04/07/2026, 4:14:11 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 4 message channels for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db [Nest] 50057 - 04/07/2026, 4:14:11 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 4 calendar channels for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db [Nest] 50057 - 04/07/2026, 4:14:11 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Migrated 10 message folders for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db [Nest] 50057 - 04/07/2026, 4:14:11 PM LOG [MigrateMessagingInfrastructureToMetadataCommand] Command completed! ```
This commit is contained in:
@@ -1736,6 +1736,7 @@ enum FeatureFlagKey {
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_DRAFT_EMAIL_ENABLED
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED
|
||||
IS_USAGE_ANALYTICS_ENABLED
|
||||
IS_RICH_TEXT_V1_MIGRATED
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
|
||||
|
||||
@@ -1429,7 +1429,7 @@ export interface PublicFeatureFlag {
|
||||
__typename: 'PublicFeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
|
||||
export interface ClientConfigMaintenanceMode {
|
||||
startAt: Scalars['DateTime']
|
||||
@@ -9149,6 +9149,7 @@ export const enumFeatureFlagKey = {
|
||||
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
|
||||
IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const,
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
|
||||
IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const,
|
||||
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
|
||||
|
||||
@@ -1730,6 +1730,7 @@ export type FeatureFlag = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED',
|
||||
IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED',
|
||||
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { Command } from 'nest-commander';
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-21:migrate-messaging-infrastructure-to-metadata',
|
||||
description:
|
||||
'Backfill connectedAccount, messageChannel, calendarChannel, and messageFolder to core metadata schema',
|
||||
})
|
||||
export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(CalendarChannelEntity)
|
||||
private readonly calendarChannelRepository: Repository<CalendarChannelEntity>,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(
|
||||
`Messaging infrastructure migration already completed for workspace ${workspaceId}. Skipping.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const connectedAccountWorkspaceRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const messageChannelWorkspaceRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<MessageChannelEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const calendarChannelWorkspaceRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<CalendarChannelEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const messageFolderWorkspaceRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
const connectedAccounts = await connectedAccountWorkspaceRepository.find();
|
||||
const messageChannels = await messageChannelWorkspaceRepository.find();
|
||||
const calendarChannels = await calendarChannelWorkspaceRepository.find();
|
||||
const messageFolders = await messageFolderWorkspaceRepository.find();
|
||||
|
||||
const workspaceMemberIdToUserWorkspaceIdMap =
|
||||
await this.buildWorkspaceMemberIdToUserWorkspaceIdMap(workspaceId);
|
||||
|
||||
const connectedAccountsWithMissingHandle = connectedAccounts.filter(
|
||||
(account) => !account.handle,
|
||||
);
|
||||
const connectedAccountsWithUnresolvedOwner = connectedAccounts.filter(
|
||||
(account) =>
|
||||
!workspaceMemberIdToUserWorkspaceIdMap.has(account.accountOwnerId),
|
||||
);
|
||||
const messageChannelsWithMissingHandle = messageChannels.filter(
|
||||
(channel) => !channel.handle,
|
||||
);
|
||||
const calendarChannelsWithMissingHandle = calendarChannels.filter(
|
||||
(channel) => !channel.handle,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Workspace ${workspaceId}: ` +
|
||||
`${connectedAccounts.length} connected accounts, ` +
|
||||
`${messageChannels.length} message channels, ` +
|
||||
`${calendarChannels.length} calendar channels, ` +
|
||||
`${messageFolders.length} message folders`,
|
||||
);
|
||||
|
||||
if (connectedAccountsWithMissingHandle.length > 0) {
|
||||
this.logger.warn(
|
||||
`[DRY RUN] ${connectedAccountsWithMissingHandle.length} connected accounts have empty handle`,
|
||||
);
|
||||
}
|
||||
|
||||
if (connectedAccountsWithUnresolvedOwner.length > 0) {
|
||||
this.logger.warn(
|
||||
`[DRY RUN] ${connectedAccountsWithUnresolvedOwner.length} connected accounts have unresolvable accountOwnerId (no matching userWorkspace)`,
|
||||
);
|
||||
}
|
||||
|
||||
if (messageChannelsWithMissingHandle.length > 0) {
|
||||
this.logger.warn(
|
||||
`[DRY RUN] ${messageChannelsWithMissingHandle.length} message channels have empty handle`,
|
||||
);
|
||||
}
|
||||
|
||||
if (calendarChannelsWithMissingHandle.length > 0) {
|
||||
this.logger.warn(
|
||||
`[DRY RUN] ${calendarChannelsWithMissingHandle.length} calendar channels have empty handle`,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let migratedConnectedAccountIds = new Set(
|
||||
connectedAccounts.map((account) => account.id),
|
||||
);
|
||||
let migratedMessageChannelIds = new Set(
|
||||
messageChannels.map((channel) => channel.id),
|
||||
);
|
||||
|
||||
if (connectedAccounts.length > 0) {
|
||||
const coreConnectedAccounts = connectedAccounts
|
||||
.filter((workspaceEntity) => {
|
||||
const userWorkspaceId = workspaceMemberIdToUserWorkspaceIdMap.get(
|
||||
workspaceEntity.accountOwnerId,
|
||||
);
|
||||
|
||||
if (!userWorkspaceId) {
|
||||
this.logger.warn(
|
||||
`Skipping connected account ${workspaceEntity.id}: no userWorkspace found for workspaceMember ${workspaceEntity.accountOwnerId}`,
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((workspaceEntity) => {
|
||||
const handleAliases = isNonEmptyString(workspaceEntity.handleAliases)
|
||||
? workspaceEntity.handleAliases
|
||||
.split(',')
|
||||
.map((alias) => alias.trim())
|
||||
: null;
|
||||
|
||||
return {
|
||||
id: workspaceEntity.id,
|
||||
handle: workspaceEntity.handle ?? '',
|
||||
provider: workspaceEntity.provider,
|
||||
accessToken: workspaceEntity.accessToken,
|
||||
refreshToken: workspaceEntity.refreshToken,
|
||||
lastCredentialsRefreshedAt:
|
||||
workspaceEntity.lastCredentialsRefreshedAt,
|
||||
authFailedAt: workspaceEntity.authFailedAt,
|
||||
handleAliases,
|
||||
scopes: workspaceEntity.scopes,
|
||||
connectionParameters:
|
||||
workspaceEntity.connectionParameters as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null,
|
||||
userWorkspaceId: workspaceMemberIdToUserWorkspaceIdMap.get(
|
||||
workspaceEntity.accountOwnerId,
|
||||
)!,
|
||||
workspaceId,
|
||||
createdAt: workspaceEntity.createdAt,
|
||||
updatedAt: workspaceEntity.updatedAt,
|
||||
};
|
||||
});
|
||||
|
||||
if (coreConnectedAccounts.length > 0) {
|
||||
await this.connectedAccountRepository.save(coreConnectedAccounts);
|
||||
this.logger.log(
|
||||
`Migrated ${coreConnectedAccounts.length} connected accounts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
migratedConnectedAccountIds = new Set(
|
||||
coreConnectedAccounts.map((account) => account.id),
|
||||
);
|
||||
}
|
||||
|
||||
if (messageChannels.length > 0) {
|
||||
const coreMessageChannels = messageChannels
|
||||
.filter((workspaceEntity) =>
|
||||
migratedConnectedAccountIds.has(workspaceEntity.connectedAccountId),
|
||||
)
|
||||
.map((workspaceEntity) => ({
|
||||
id: workspaceEntity.id,
|
||||
visibility: workspaceEntity.visibility,
|
||||
handle: workspaceEntity.handle ?? '',
|
||||
type: workspaceEntity.type,
|
||||
isContactAutoCreationEnabled:
|
||||
workspaceEntity.isContactAutoCreationEnabled,
|
||||
contactAutoCreationPolicy: workspaceEntity.contactAutoCreationPolicy,
|
||||
messageFolderImportPolicy: workspaceEntity.messageFolderImportPolicy,
|
||||
excludeNonProfessionalEmails:
|
||||
workspaceEntity.excludeNonProfessionalEmails,
|
||||
excludeGroupEmails: workspaceEntity.excludeGroupEmails,
|
||||
pendingGroupEmailsAction: workspaceEntity.pendingGroupEmailsAction,
|
||||
isSyncEnabled: workspaceEntity.isSyncEnabled,
|
||||
syncCursor: workspaceEntity.syncCursor,
|
||||
syncedAt: workspaceEntity.syncedAt
|
||||
? new Date(workspaceEntity.syncedAt)
|
||||
: null,
|
||||
syncStatus: workspaceEntity.syncStatus ?? 'NOT_SYNCED',
|
||||
syncStage: workspaceEntity.syncStage,
|
||||
syncStageStartedAt: workspaceEntity.syncStageStartedAt
|
||||
? new Date(workspaceEntity.syncStageStartedAt)
|
||||
: null,
|
||||
throttleFailureCount: workspaceEntity.throttleFailureCount,
|
||||
throttleRetryAfter: workspaceEntity.throttleRetryAfter
|
||||
? new Date(workspaceEntity.throttleRetryAfter)
|
||||
: null,
|
||||
connectedAccountId: workspaceEntity.connectedAccountId,
|
||||
workspaceId,
|
||||
createdAt: workspaceEntity.createdAt,
|
||||
updatedAt: workspaceEntity.updatedAt,
|
||||
}));
|
||||
|
||||
if (coreMessageChannels.length > 0) {
|
||||
await this.messageChannelRepository.save(
|
||||
coreMessageChannels as unknown as MessageChannelEntity[],
|
||||
);
|
||||
this.logger.log(
|
||||
`Migrated ${coreMessageChannels.length} message channels for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
migratedMessageChannelIds = new Set(
|
||||
coreMessageChannels.map((channel) => channel.id),
|
||||
);
|
||||
}
|
||||
|
||||
if (calendarChannels.length > 0) {
|
||||
const coreCalendarChannels = calendarChannels
|
||||
.filter((workspaceEntity) =>
|
||||
migratedConnectedAccountIds.has(workspaceEntity.connectedAccountId),
|
||||
)
|
||||
.map((workspaceEntity) => ({
|
||||
id: workspaceEntity.id,
|
||||
handle: workspaceEntity.handle ?? '',
|
||||
syncStatus: workspaceEntity.syncStatus ?? 'NOT_SYNCED',
|
||||
syncStage: workspaceEntity.syncStage,
|
||||
visibility: workspaceEntity.visibility,
|
||||
isContactAutoCreationEnabled:
|
||||
workspaceEntity.isContactAutoCreationEnabled,
|
||||
contactAutoCreationPolicy: workspaceEntity.contactAutoCreationPolicy,
|
||||
isSyncEnabled: workspaceEntity.isSyncEnabled,
|
||||
syncCursor: workspaceEntity.syncCursor,
|
||||
syncedAt: workspaceEntity.syncedAt
|
||||
? new Date(workspaceEntity.syncedAt)
|
||||
: null,
|
||||
syncStageStartedAt: workspaceEntity.syncStageStartedAt
|
||||
? new Date(workspaceEntity.syncStageStartedAt)
|
||||
: null,
|
||||
throttleFailureCount: workspaceEntity.throttleFailureCount,
|
||||
connectedAccountId: workspaceEntity.connectedAccountId,
|
||||
workspaceId,
|
||||
createdAt: workspaceEntity.createdAt,
|
||||
updatedAt: workspaceEntity.updatedAt,
|
||||
}));
|
||||
|
||||
await this.calendarChannelRepository.save(
|
||||
coreCalendarChannels as unknown as CalendarChannelEntity[],
|
||||
);
|
||||
this.logger.log(
|
||||
`Migrated ${coreCalendarChannels.length} calendar channels for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (messageFolders.length > 0) {
|
||||
const externalIdToFolderIdMap = new Map(
|
||||
messageFolders.map((folder) => [folder.externalId, folder.id]),
|
||||
);
|
||||
|
||||
const coreMessageFolders = messageFolders
|
||||
.filter((workspaceEntity) =>
|
||||
migratedMessageChannelIds.has(workspaceEntity.messageChannelId),
|
||||
)
|
||||
.map((workspaceEntity) => {
|
||||
let resolvedParentFolderId: string | null = null;
|
||||
|
||||
if (isNonEmptyString(workspaceEntity.parentFolderId)) {
|
||||
resolvedParentFolderId =
|
||||
externalIdToFolderIdMap.get(workspaceEntity.parentFolderId) ??
|
||||
null;
|
||||
|
||||
if (!resolvedParentFolderId) {
|
||||
this.logger.warn(
|
||||
`Message folder ${workspaceEntity.id}: could not resolve parentFolderId externalId "${workspaceEntity.parentFolderId}" to a UUID`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: workspaceEntity.id,
|
||||
name: workspaceEntity.name,
|
||||
syncCursor: workspaceEntity.syncCursor,
|
||||
isSentFolder: workspaceEntity.isSentFolder,
|
||||
isSynced: workspaceEntity.isSynced,
|
||||
parentFolderId: resolvedParentFolderId,
|
||||
externalId: workspaceEntity.externalId,
|
||||
pendingSyncAction: workspaceEntity.pendingSyncAction,
|
||||
messageChannelId: workspaceEntity.messageChannelId,
|
||||
workspaceId,
|
||||
createdAt: workspaceEntity.createdAt,
|
||||
updatedAt: workspaceEntity.updatedAt,
|
||||
};
|
||||
});
|
||||
|
||||
await this.messageFolderRepository.save(
|
||||
coreMessageFolders as unknown as MessageFolderEntity[],
|
||||
);
|
||||
this.logger.log(
|
||||
`Migrated ${coreMessageFolders.length} message folders for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async buildWorkspaceMemberIdToUserWorkspaceIdMap(
|
||||
workspaceId: string,
|
||||
): Promise<Map<string, string>> {
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
);
|
||||
|
||||
const workspaceMembers = await workspaceMemberRepository.find();
|
||||
const userWorkspaces = await this.userWorkspaceRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'userId'],
|
||||
});
|
||||
|
||||
const userWorkspaceIdByUserId = new Map(
|
||||
userWorkspaces.map((userWorkspace) => [
|
||||
userWorkspace.userId,
|
||||
userWorkspace.id,
|
||||
]),
|
||||
);
|
||||
|
||||
return new Map(
|
||||
workspaceMembers
|
||||
.filter((member) => userWorkspaceIdByUserId.has(member.userId))
|
||||
.map((member) => [
|
||||
member.id,
|
||||
userWorkspaceIdByUserId.get(member.userId)!,
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
+11
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { AddComposeEmailCommandMenuItemCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-add-compose-email-command-menu-item.command';
|
||||
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-migrate-messaging-infrastructure-to-metadata.command';
|
||||
import { AddGlobalKeyValuePairUniqueIndexCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-add-global-key-value-pair-unique-index.command';
|
||||
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-backfill-datasource-to-workspace.command';
|
||||
import { BackfillMessageThreadSubjectCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-backfill-message-thread-subject.command';
|
||||
@@ -15,9 +16,13 @@ import { MigrateMessageFolderParentIdToExternalIdCommand } from 'src/database/co
|
||||
import { UpdateEditLayoutCommandMenuItemLabelCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-update-edit-layout-command-menu-item-label.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.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 { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
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';
|
||||
@@ -28,7 +33,11 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
DataSourceEntity,
|
||||
CalendarChannelEntity,
|
||||
ConnectedAccountEntity,
|
||||
MessageChannelEntity,
|
||||
MessageFolderEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceCacheModule,
|
||||
@@ -50,6 +59,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
UpdateEditLayoutCommandMenuItemLabelCommand,
|
||||
DropWorkspaceMessagingFksCommand,
|
||||
MigrateMessageFolderParentIdToExternalIdCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
],
|
||||
exports: [
|
||||
AddComposeEmailCommandMenuItemCommand,
|
||||
@@ -63,6 +73,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
UpdateEditLayoutCommandMenuItemLabelCommand,
|
||||
DropWorkspaceMessagingFksCommand,
|
||||
MigrateMessageFolderParentIdToExternalIdCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
],
|
||||
})
|
||||
export class V1_21_UpgradeVersionCommandModule {}
|
||||
|
||||
+1
@@ -240,6 +240,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_EMAILING_DOMAIN_ENABLED: false,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: false,
|
||||
IS_DRAFT_EMAIL_ENABLED: false,
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED: false,
|
||||
IS_USAGE_ANALYTICS_ENABLED: false,
|
||||
IS_RICH_TEXT_V1_MIGRATED: false,
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: false,
|
||||
|
||||
@@ -10,6 +10,7 @@ export enum FeatureFlagKey {
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED',
|
||||
IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED',
|
||||
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
|
||||
|
||||
Reference in New Issue
Block a user