5622 add a syncemail onboarding step (#5689)
- add sync email onboarding step - refactor calendar and email visibility enums - add a new table `keyValuePair` in `core` schema - add a new resolved boolean field `skipSyncEmail` in current user https://github.com/twentyhq/twenty/assets/29927851/de791475-5bfe-47f9-8e90-76c349fba56f
This commit is contained in:
@ -15,10 +15,11 @@ import { StopDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data
|
||||
import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command';
|
||||
import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command';
|
||||
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
|
||||
import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/0-20-update-message-channel-sync-status-enum.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { UpdateMessageChannelVisibilityEnumCommand } from 'src/database/commands/update-message-channel-visibility-enum.command';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
||||
import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/0-20-update-message-channel-sync-status-enum.command';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Module({
|
||||
@ -45,6 +46,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
||||
ConfirmationQuestion,
|
||||
StartDataSeedDemoWorkspaceCronCommand,
|
||||
StopDataSeedDemoWorkspaceCronCommand,
|
||||
UpdateMessageChannelVisibilityEnumCommand,
|
||||
UpdateMessageChannelSyncStatusEnumCommand,
|
||||
],
|
||||
})
|
||||
|
||||
@ -0,0 +1,166 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
|
||||
interface UpdateMessageChannelVisibilityEnumCommandOptions {
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
@Command({
|
||||
name: 'migrate-0.20:update-message-channel-visibility-enum',
|
||||
description:
|
||||
'Change the messageChannel visibility type and update records.visibility',
|
||||
})
|
||||
export class UpdateMessageChannelVisibilityEnumCommand extends CommandRunner {
|
||||
private readonly logger = new Logger(
|
||||
UpdateMessageChannelVisibilityEnumCommand.name,
|
||||
);
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description: 'workspace id. Command runs on all workspaces if not provided',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceId(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
async run(
|
||||
_passedParam: string[],
|
||||
options: UpdateMessageChannelVisibilityEnumCommandOptions,
|
||||
): Promise<void> {
|
||||
let workspaceIds: string[] = [];
|
||||
|
||||
if (options.workspaceId) {
|
||||
workspaceIds = [options.workspaceId];
|
||||
} else {
|
||||
workspaceIds = (await this.workspaceRepository.find()).map(
|
||||
(workspace) => workspace.id,
|
||||
);
|
||||
}
|
||||
|
||||
if (!workspaceIds.length) {
|
||||
this.logger.log(chalk.yellow('No workspace found'));
|
||||
|
||||
return;
|
||||
} else {
|
||||
this.logger.log(
|
||||
chalk.green(`Running command on ${workspaceIds.length} workspaces`),
|
||||
);
|
||||
}
|
||||
|
||||
for (const workspaceId of workspaceIds) {
|
||||
const dataSourceMetadatas =
|
||||
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
for (const dataSourceMetadata of dataSourceMetadatas) {
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
if (workspaceDataSource) {
|
||||
const queryRunner = workspaceDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
const newMessageChannelVisibilities = Object.values(
|
||||
MessageChannelVisibility,
|
||||
);
|
||||
|
||||
try {
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "${dataSourceMetadata.schema}"."messageChannel_visibility_enum" RENAME TO "messageChannel_visibility_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "${
|
||||
dataSourceMetadata.schema
|
||||
}"."messageChannel_visibility_enum" AS ENUM ('${newMessageChannelVisibilities.join(
|
||||
"','",
|
||||
)}')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" TYPE text`,
|
||||
);
|
||||
for (const newMessageChannelVisibility of newMessageChannelVisibilities) {
|
||||
await queryRunner.query(
|
||||
`UPDATE "${
|
||||
dataSourceMetadata.schema
|
||||
}"."messageChannel" SET "visibility" = '${newMessageChannelVisibility}' WHERE "visibility" = '${newMessageChannelVisibility.toLowerCase()}'`,
|
||||
);
|
||||
}
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" TYPE "${dataSourceMetadata.schema}"."messageChannel_visibility_enum" USING "visibility"::text::"${dataSourceMetadata.schema}"."messageChannel_visibility_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "visibility" SET DEFAULT '${MessageChannelVisibility.SHARE_EVERYTHING}'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "${dataSourceMetadata.schema}"."messageChannel_visibility_enum_old"`,
|
||||
);
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.log(
|
||||
chalk.red(`Running command on workspace ${workspaceId} failed`),
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
|
||||
|
||||
const visibilityFieldsMetadata = await this.fieldMetadataRepository.find({
|
||||
where: { name: 'visibility', workspaceId },
|
||||
});
|
||||
|
||||
for (const visibilityFieldMetadata of visibilityFieldsMetadata) {
|
||||
const newOptions = visibilityFieldMetadata.options.map((option) => {
|
||||
return { ...option, value: option.value.toUpperCase() };
|
||||
});
|
||||
|
||||
const newDefaultValue =
|
||||
typeof visibilityFieldMetadata.defaultValue === 'string'
|
||||
? visibilityFieldMetadata.defaultValue.toUpperCase()
|
||||
: visibilityFieldMetadata.defaultValue;
|
||||
|
||||
await this.fieldMetadataRepository.update(visibilityFieldMetadata.id, {
|
||||
defaultValue: newDefaultValue,
|
||||
options: newOptions,
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
chalk.green(`Running command on workspace ${workspaceId} done`),
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(chalk.green(`Command completed!`));
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account';
|
||||
import { CalendarChannelVisibility } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
const tableName = 'calendarChannel';
|
||||
|
||||
@ -25,7 +26,7 @@ export const seedCalendarChannels = async (
|
||||
id: '59efdefe-a40f-4faf-bb9f-c6f9945b8203',
|
||||
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.TIM,
|
||||
handle: 'tim@apple.com',
|
||||
visibility: 'SHARE_EVERYTHING',
|
||||
visibility: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
isContactAutoCreationEnabled: true,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelVisibility,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
const tableName = 'messageChannel';
|
||||
|
||||
@ -41,8 +44,8 @@ export const seedMessageChannel = async (
|
||||
type: 'email',
|
||||
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.TIM,
|
||||
handle: 'tim@apple.dev',
|
||||
visibility: 'share_everything',
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncSubStatus: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
},
|
||||
{
|
||||
id: DEV_SEED_MESSAGE_CHANNEL_IDS.JONY,
|
||||
@ -53,8 +56,8 @@ export const seedMessageChannel = async (
|
||||
type: 'email',
|
||||
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.JONY,
|
||||
handle: 'jony.ive@apple.dev',
|
||||
visibility: 'share_everything',
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncSubStatus: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
},
|
||||
{
|
||||
id: DEV_SEED_MESSAGE_CHANNEL_IDS.PHIL,
|
||||
@ -65,8 +68,8 @@ export const seedMessageChannel = async (
|
||||
type: 'email',
|
||||
connectedAccountId: DEV_SEED_CONNECTED_ACCOUNT_IDS.PHIL,
|
||||
handle: 'phil.schiler@apple.dev',
|
||||
visibility: 'share_everything',
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncSubStatus: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddKeyValuePairTable1717425967770 implements MigrationInterface {
|
||||
name = 'AddKeyValuePairTable1717425967770';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "core"."keyValuePair" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid, "workspaceId" uuid, "key" text NOT NULL, "value" text, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "IndexOnKeyUserIdWorkspaceIdUnique" UNIQUE ("key", "userId", "workspaceId"), CONSTRAINT "PK_c5a1ca828435d3eaf8f9361ed4b" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."keyValuePair" ADD CONSTRAINT "FK_0dae35d1c0fbdda6495be4ae71a" FOREIGN KEY ("userId") REFERENCES "core"."user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."keyValuePair" ADD CONSTRAINT "FK_c137e3d8b3980901e114941daa2" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."keyValuePair" DROP CONSTRAINT "FK_c137e3d8b3980901e114941daa2"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."keyValuePair" DROP CONSTRAINT "FK_0dae35d1c0fbdda6495be4ae71a"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "core"."keyValuePair"`);
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
@ -29,6 +30,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
Workspace,
|
||||
UserWorkspace,
|
||||
AppToken,
|
||||
KeyValuePair,
|
||||
FeatureFlagEntity,
|
||||
BillingSubscription,
|
||||
BillingSubscriptionItem,
|
||||
|
||||
Reference in New Issue
Block a user