From 6c34ef9a145da8b7df3c2c7bea1b61b61c8ec6a2 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 23 Jul 2024 14:13:16 +0200 Subject: [PATCH] Simplify ORM (#6373) This PR refactors the ORM-Manager to simplify and unify the datasource creation. I'm deprecating all usages if InjectWorkspaceDatasource and InjectWorkspaceRepository as we can't be sure they are up-to-date --- .../commands/database-command.module.ts | 4 - ...message-channel-visibility-enum.command.ts | 166 ------------- ...o-views-with-deprecated-address.command.ts | 180 -------------- ...-object-metadata-id-standard-id.command.ts | 101 -------- ...-default-values-and-null-values.command.ts | 165 ------------- ...message-channel-sync-stage-enum.command.ts | 233 ------------------ ...essage-channel-sync-status-enum.command.ts | 217 ---------------- .../0-22/0-22-upgrade-version.command.ts | 59 ----- .../0-22/0-22-upgrade-version.module.ts | 56 ----- ...23-migrate-link-fields-to-links.command.ts | 4 +- .../auth/services/google-apis.service.ts | 106 ++++---- .../inject-workspace-datasource.decorator.ts | 7 - .../inject-workspace-repository.decorator.ts | 9 - .../factories/workspace-datasource.factory.ts | 133 +++++++--- .../twenty-orm/twenty-orm-core.module.ts | 115 +-------- .../twenty-orm/twenty-orm-global.manager.ts | 105 +------- .../engine/twenty-orm/twenty-orm.constants.ts | 2 - .../engine/twenty-orm/twenty-orm.manager.ts | 159 ++---------- .../get-workspace-repository-token.util.ts | 24 -- .../blocklist-reimport-calendar-events.job.ts | 7 +- .../calendar-event-list-fetch.cron.job.ts | 13 +- .../services/calendar-save-events.service.ts | 7 +- .../src/modules/view/services/view.service.ts | 5 +- 23 files changed, 205 insertions(+), 1672 deletions(-) delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-20/0-20-update-message-channel-visibility-enum.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-add-new-address-field-to-views-with-deprecated-address.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-fix-object-metadata-id-standard-id.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-boolean-fields-null-default-values-and-null-values.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-stage-enum.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-status-enum.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.command.ts delete mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module.ts delete mode 100644 packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator.ts delete mode 100644 packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-repository.decorator.ts delete mode 100644 packages/twenty-server/src/engine/twenty-orm/twenty-orm.constants.ts delete mode 100644 packages/twenty-server/src/engine/twenty-orm/utils/get-workspace-repository-token.util.ts diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index f3a1e61ea..10ce74e95 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -7,8 +7,6 @@ import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-de import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question'; -import { UpdateMessageChannelVisibilityEnumCommand } from 'src/database/commands/upgrade-version/0-20/0-20-update-message-channel-visibility-enum.command'; -import { UpgradeTo0_22CommandModule } from 'src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module'; import { UpgradeTo0_23CommandModule } from 'src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module'; import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; @@ -47,7 +45,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp DataSeedDemoWorkspaceModule, WorkspaceCacheVersionModule, // Upgrades - UpgradeTo0_22CommandModule, UpgradeTo0_23CommandModule, ], providers: [ @@ -57,7 +54,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp ConfirmationQuestion, StartDataSeedDemoWorkspaceCronCommand, StopDataSeedDemoWorkspaceCronCommand, - UpdateMessageChannelVisibilityEnumCommand, ], }) export class DatabaseCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-20/0-20-update-message-channel-visibility-enum.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-20/0-20-update-message-channel-visibility-enum.command.ts deleted file mode 100644 index 2241de4d0..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-20/0-20-update-message-channel-visibility-enum.command.ts +++ /dev/null @@ -1,166 +0,0 @@ -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, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - 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 { - 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!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-add-new-address-field-to-views-with-deprecated-address.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-add-new-address-field-to-views-with-deprecated-address.command.ts deleted file mode 100644 index 4f4522b03..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-add-new-address-field-to-views-with-deprecated-address.command.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import isEmpty from 'lodash.isempty'; -import { Command, CommandRunner, Option } from 'nest-commander'; -import { Repository } from 'typeorm'; - -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -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'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { WorkspaceStatusService } from 'src/engine/workspace-manager/workspace-status/services/workspace-status.service'; -import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; - -interface AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'migrate-0.22:add-new-address-field-to-views-with-deprecated-address-field', - description: 'Adding new field Address to views containing old address field', -}) -export class AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand extends CommandRunner { - private readonly logger = new Logger( - AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand.name, - ); - constructor( - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - private readonly typeORMService: TypeORMService, - private readonly dataSourceService: DataSourceService, - private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, - private readonly twentyORMManager: TwentyORMManager, - private readonly workspaceStatusService: WorkspaceStatusService, - ) { - 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: AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommandOptions, - ): Promise { - // This command can be generic-ified turning the below consts in options - const deprecatedFieldStandardId = - COMPANY_STANDARD_FIELD_IDS.address_deprecated; - const newFieldStandardId = COMPANY_STANDARD_FIELD_IDS.address; - - this.logger.log('running'); - let workspaceIds: string[] = []; - - if (options.workspaceId) { - workspaceIds = [options.workspaceId]; - } else { - const activeWorkspaceIds = - await this.workspaceStatusService.getActiveWorkspaceIds(); - - workspaceIds = activeWorkspaceIds; - } - - 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) { - this.logger.log(`Running command for workspace ${workspaceId}`); - try { - const viewFieldRepository = - await this.twentyORMManager.getRepositoryForWorkspace( - workspaceId, - ViewFieldWorkspaceEntity, - ); - - const dataSourceMetadatas = - await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId( - workspaceId, - ); - - for (const dataSourceMetadata of dataSourceMetadatas) { - const workspaceDataSource = - await this.typeORMService.connectToDataSource(dataSourceMetadata); - - if (workspaceDataSource) { - const newAddressField = await this.fieldMetadataRepository.findBy({ - workspaceId, - standardId: newFieldStandardId, - }); - - if (isEmpty(newAddressField)) { - this.logger.log( - `Error - missing new Address standard field of type Address, please run workspace-sync-metadata on your workspace (${workspaceId}) before running this command`, - ); - continue; - } - - const addressDeprecatedField = - await this.fieldMetadataRepository.findOneBy({ - workspaceId, - standardId: deprecatedFieldStandardId, - }); - - if (isEmpty(addressDeprecatedField)) { - continue; - } - - const viewsWithAddressDeprecatedField = - await viewFieldRepository.find({ - where: { - fieldMetadataId: addressDeprecatedField.id, - isVisible: true, - }, - }); - - for (const viewWithAddressDeprecatedField of viewsWithAddressDeprecatedField) { - const viewId = viewWithAddressDeprecatedField.viewId; - - const newAddressFieldInThisView = - await viewFieldRepository.findBy({ - fieldMetadataId: newAddressField[0].id, - viewId: viewWithAddressDeprecatedField.viewId as string, - isVisible: true, - }); - - if (!isEmpty(newAddressFieldInThisView)) { - continue; - } - - this.logger.log( - `Adding new address field to view ${viewId} for workspace ${workspaceId}...`, - ); - const newViewField = viewFieldRepository.create({ - viewId: viewWithAddressDeprecatedField.viewId, - fieldMetadataId: newAddressField[0].id, - position: viewWithAddressDeprecatedField.position - 0.5, - isVisible: true, - }); - - await viewFieldRepository.save(newViewField); - this.logger.log( - `New address field successfully added to view ${viewId} for workspace ${workspaceId}`, - ); - } - } - } - - await this.workspaceCacheVersionService.incrementVersion(workspaceId); - - this.logger.log( - chalk.green(`Running command on workspace ${workspaceId} done`), - ); - } catch (error) { - this.logger.log( - chalk.red( - `Running command on workspace ${workspaceId} failed with error: ${error}`, - ), - ); - continue; - } - - this.logger.log(chalk.green(`Command completed!`)); - } - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-fix-object-metadata-id-standard-id.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-fix-object-metadata-id-standard-id.command.ts deleted file mode 100644 index 213892637..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-fix-object-metadata-id-standard-id.command.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command, CommandRunner, Option } from 'nest-commander'; -import { DataSource, Repository } from 'typeorm'; - -import { Workspace } from 'src/engine/core-modules/workspace/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'; -import { AUDIT_LOGS_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; - -interface FixObjectMetadataIdStandardIdCommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.22:fix-object-metadata-id-standard-id', - description: 'Fix object metadata id standard id', -}) -export class FixObjectMetadataIdStandardIdCommand extends CommandRunner { - private readonly logger = new Logger( - FixObjectMetadataIdStandardIdCommand.name, - ); - constructor( - @InjectRepository(Workspace, 'core') - private readonly workspaceRepository: Repository, - private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, - @InjectDataSource('metadata') - private readonly metadataDataSource: DataSource, - ) { - 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: FixObjectMetadataIdStandardIdCommandOptions, - ): Promise { - const workspaceIds = options.workspaceId - ? [options.workspaceId] - : (await this.workspaceRepository.find()).map( - (workspace) => workspace.id, - ); - - if (!workspaceIds.length) { - this.logger.log(chalk.yellow('No workspace found')); - - return; - } - - this.logger.log( - chalk.green(`Running command on ${workspaceIds.length} workspaces`), - ); - - const metadataQueryRunner = this.metadataDataSource.createQueryRunner(); - - await metadataQueryRunner.connect(); - - const fieldMetadataRepository = - metadataQueryRunner.manager.getRepository(FieldMetadataEntity); - - for (const workspaceId of workspaceIds) { - try { - await metadataQueryRunner.startTransaction(); - - await fieldMetadataRepository.delete({ - workspaceId, - standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.objectName, - name: 'objectMetadataId', - }); - - await metadataQueryRunner.commitTransaction(); - } catch (error) { - await metadataQueryRunner.rollbackTransaction(); - this.logger.log( - chalk.red(`Running command on workspace ${workspaceId} failed`), - ); - throw error; - } - - await this.workspaceCacheVersionService.incrementVersion(workspaceId); - - this.logger.log( - chalk.green(`Running command on workspace ${workspaceId} done`), - ); - } - - await metadataQueryRunner.release(); - - this.logger.log(chalk.green(`Command completed!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-boolean-fields-null-default-values-and-null-values.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-boolean-fields-null-default-values-and-null-values.command.ts deleted file mode 100644 index 3809600b5..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-boolean-fields-null-default-values-and-null-values.command.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command, CommandRunner, Option } from 'nest-commander'; -import { DataSource, IsNull, Repository } from 'typeorm'; - -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { - FieldMetadataEntity, - FieldMetadataType, -} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; - -interface UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.22:update-boolean-field-null-default-values-and-null-values', - description: - 'Update boolean fields null default values and null values to false', -}) -export class UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommand extends CommandRunner { - private readonly logger = new Logger( - UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommand.name, - ); - constructor( - @InjectRepository(Workspace, 'core') - private readonly workspaceRepository: Repository, - private readonly typeORMService: TypeORMService, - private readonly dataSourceService: DataSourceService, - private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, - @InjectDataSource('metadata') - private readonly metadataDataSource: DataSource, - ) { - 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: UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommandOptions, - ): Promise { - const workspaceIds = options.workspaceId - ? [options.workspaceId] - : (await this.workspaceRepository.find()).map( - (workspace) => workspace.id, - ); - - if (!workspaceIds.length) { - this.logger.log(chalk.yellow('No workspace found')); - - return; - } - - this.logger.log( - chalk.green(`Running command on ${workspaceIds.length} workspaces`), - ); - - for (const workspaceId of workspaceIds) { - const dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( - workspaceId, - ); - - if (!dataSourceMetadata) { - this.logger.log( - `Could not find dataSourceMetadata for workspace ${workspaceId}`, - ); - continue; - } - - const workspaceDataSource = - await this.typeORMService.connectToDataSource(dataSourceMetadata); - - if (!workspaceDataSource) { - throw new Error( - `Could not connect to dataSource for workspace ${workspaceId}`, - ); - } - - const workspaceQueryRunner = workspaceDataSource.createQueryRunner(); - const metadataQueryRunner = this.metadataDataSource.createQueryRunner(); - - await workspaceQueryRunner.connect(); - await metadataQueryRunner.connect(); - - await workspaceQueryRunner.startTransaction(); - await metadataQueryRunner.startTransaction(); - - try { - const fieldMetadataRepository = - metadataQueryRunner.manager.getRepository(FieldMetadataEntity); - - const booleanFieldsWithoutDefaultValue = - await fieldMetadataRepository.find({ - where: { - workspaceId, - type: FieldMetadataType.BOOLEAN, - defaultValue: IsNull(), - }, - relations: ['object'], - }); - - for (const booleanField of booleanFieldsWithoutDefaultValue) { - if (!booleanField.object) { - this.logger.log( - `Could not find objectMetadataItem for field ${booleanField.id}`, - ); - continue; - } - - // Could be done via a batch update but it's safer in this context to run it sequentially with the ALTER TABLE - await fieldMetadataRepository.update(booleanField.id, { - defaultValue: false, - }); - - const fieldName = booleanField.name; - const tableName = computeObjectTargetTable(booleanField.object); - - await workspaceQueryRunner.query( - `UPDATE "${dataSourceMetadata.schema}"."${tableName}" SET "${fieldName}" = 'false' WHERE "${fieldName}" IS NULL`, - ); - - await workspaceQueryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."${tableName}" ALTER COLUMN "${fieldName}" SET DEFAULT false;`, - ); - } - - await workspaceQueryRunner.commitTransaction(); - await metadataQueryRunner.commitTransaction(); - } catch (error) { - await workspaceQueryRunner.rollbackTransaction(); - await metadataQueryRunner.rollbackTransaction(); - this.logger.log( - chalk.red(`Running command on workspace ${workspaceId} failed`), - ); - throw error; - } finally { - await workspaceQueryRunner.release(); - await metadataQueryRunner.release(); - } - - await this.workspaceCacheVersionService.incrementVersion(workspaceId); - - this.logger.log( - chalk.green(`Running command on workspace ${workspaceId} done`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-stage-enum.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-stage-enum.command.ts deleted file mode 100644 index e9de0891e..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-stage-enum.command.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command, CommandRunner, Option } from 'nest-commander'; -import { Repository } from 'typeorm'; -import { v4 } from 'uuid'; - -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; -import { WorkspaceStatusService } from 'src/engine/workspace-manager/workspace-status/services/workspace-status.service'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; - -interface UpdateMessageChannelSyncStageEnumCommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'migrate-0.22:update-message-channel-sync-stage-enum', - description: 'Update messageChannel syncStage', -}) -export class UpdateMessageChannelSyncStageEnumCommand extends CommandRunner { - private readonly logger = new Logger( - UpdateMessageChannelSyncStageEnumCommand.name, - ); - constructor( - private readonly workspaceStatusService: WorkspaceStatusService, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - 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: UpdateMessageChannelSyncStageEnumCommandOptions, - ): Promise { - let workspaceIds: string[] = []; - - if (options.workspaceId) { - workspaceIds = [options.workspaceId]; - } else { - workspaceIds = await this.workspaceStatusService.getActiveWorkspaceIds(); - } - - 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) { - try { - 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(); - - try { - await queryRunner.query( - `ALTER TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStage_enum" RENAME TO "messageChannel_syncStage_enum_old"`, - ); - await queryRunner.query( - `CREATE TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStage_enum" AS ENUM ( - 'FULL_MESSAGE_LIST_FETCH_PENDING', - 'PARTIAL_MESSAGE_LIST_FETCH_PENDING', - 'MESSAGE_LIST_FETCH_ONGOING', - 'MESSAGES_IMPORT_PENDING', - 'MESSAGES_IMPORT_ONGOING', - 'FAILED' - )`, - ); - - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStage" DROP DEFAULT`, - ); - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStage" TYPE text`, - ); - - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStage" TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStage_enum" USING "syncStage"::text::"${dataSourceMetadata.schema}"."messageChannel_syncStage_enum"`, - ); - - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStage" SET DEFAULT 'FULL_MESSAGE_LIST_FETCH_PENDING'`, - ); - - await queryRunner.query( - `DROP TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStage_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(); - } - } - } - - const messageChannelObjectMetadata = - await this.objectMetadataRepository.findOne({ - where: { nameSingular: 'messageChannel', workspaceId }, - }); - - if (!messageChannelObjectMetadata) { - this.logger.log( - chalk.yellow( - `Object metadata for messageChannel not found in workspace ${workspaceId}`, - ), - ); - - continue; - } - - const syncStageFieldMetadata = - await this.fieldMetadataRepository.findOne({ - where: { - name: 'syncStage', - workspaceId, - objectMetadataId: messageChannelObjectMetadata.id, - }, - }); - - if (!syncStageFieldMetadata) { - this.logger.log( - chalk.yellow( - `Field metadata for syncStage not found in workspace ${workspaceId}`, - ), - ); - - continue; - } - - const newOptions = [ - { - id: v4(), - value: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING, - label: 'Full messages list fetch pending', - position: 0, - color: 'blue', - }, - { - id: v4(), - value: MessageChannelSyncStage.PARTIAL_MESSAGE_LIST_FETCH_PENDING, - label: 'Partial messages list fetch pending', - position: 1, - color: 'blue', - }, - { - id: v4(), - value: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, - label: 'Messages list fetch ongoing', - position: 2, - color: 'orange', - }, - { - id: v4(), - value: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING, - label: 'Messages import pending', - position: 3, - color: 'blue', - }, - { - id: v4(), - value: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, - label: 'Messages import ongoing', - position: 4, - color: 'orange', - }, - { - id: v4(), - value: MessageChannelSyncStage.FAILED, - label: 'Failed', - position: 5, - color: 'red', - }, - ]; - - await this.fieldMetadataRepository.update(syncStageFieldMetadata.id, { - options: newOptions, - }); - - await this.workspaceCacheVersionService.incrementVersion(workspaceId); - - this.logger.log( - chalk.green(`Running command on workspace ${workspaceId} done`), - ); - } catch (error) { - this.logger.error( - `Migration failed for workspace ${workspaceId}: ${error.message}`, - ); - } - } - - this.logger.log(chalk.green(`Command completed!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-status-enum.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-status-enum.command.ts deleted file mode 100644 index d31a0fcd2..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-status-enum.command.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import chalk from 'chalk'; -import { Command, CommandRunner, Option } from 'nest-commander'; -import { Repository } from 'typeorm'; -import { v4 } from 'uuid'; - -import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; -import { MessageChannelSyncStatus } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; - -interface UpdateMessageChannelSyncStatusEnumCommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'migrate-0.22:update-message-channel-sync-status-enum', - description: 'Update messageChannel syncStatus', -}) -export class UpdateMessageChannelSyncStatusEnumCommand extends CommandRunner { - private readonly logger = new Logger( - UpdateMessageChannelSyncStatusEnumCommand.name, - ); - constructor( - @InjectRepository(Workspace, 'core') - private readonly workspaceRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - 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: UpdateMessageChannelSyncStatusEnumCommandOptions, - ): Promise { - 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(); - - try { - await queryRunner.query( - `ALTER TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStatus_enum" RENAME TO "messageChannel_syncStatus_enum_old"`, - ); - await queryRunner.query( - `CREATE TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStatus_enum" AS ENUM ( - 'ONGOING', - 'NOT_SYNCED', - 'COMPLETED', - 'FAILED_INSUFFICIENT_PERMISSIONS', - 'FAILED_UNKNOWN')`, - ); - - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStatus" DROP DEFAULT`, - ); - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStatus" TYPE text`, - ); - - await queryRunner.query( - `ALTER TABLE "${dataSourceMetadata.schema}"."messageChannel" ALTER COLUMN "syncStatus" TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStatus_enum" USING "syncStatus"::text::"${dataSourceMetadata.schema}"."messageChannel_syncStatus_enum"`, - ); - - await queryRunner.query( - `DROP TYPE "${dataSourceMetadata.schema}"."messageChannel_syncStatus_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(); - } - } - } - - const messageChannelObjectMetadata = - await this.objectMetadataRepository.findOne({ - where: { nameSingular: 'messageChannel', workspaceId }, - }); - - if (!messageChannelObjectMetadata) { - this.logger.log( - chalk.yellow( - `Object metadata for messageChannel not found in workspace ${workspaceId}`, - ), - ); - - continue; - } - - const syncStatusFieldMetadata = - await this.fieldMetadataRepository.findOne({ - where: { - name: 'syncStatus', - workspaceId, - objectMetadataId: messageChannelObjectMetadata.id, - }, - }); - - if (!syncStatusFieldMetadata) { - this.logger.log( - chalk.yellow( - `Field metadata for syncStatus not found in workspace ${workspaceId}`, - ), - ); - - continue; - } - - const newOptions = [ - { - id: v4(), - value: MessageChannelSyncStatus.ONGOING, - label: 'Ongoing', - position: 1, - color: 'yellow', - }, - { - id: v4(), - value: MessageChannelSyncStatus.NOT_SYNCED, - label: 'Not Synced', - position: 4, - color: 'blue', - }, - { - id: v4(), - value: MessageChannelSyncStatus.COMPLETED, - label: 'Completed', - position: 5, - color: 'green', - }, - { - id: v4(), - value: MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS, - label: 'Failed Insufficient Permissions', - position: 6, - color: 'red', - }, - { - id: v4(), - value: MessageChannelSyncStatus.FAILED_UNKNOWN, - label: 'Failed Unknown', - position: 7, - color: 'red', - }, - ]; - - await this.fieldMetadataRepository.update(syncStatusFieldMetadata.id, { - options: newOptions, - }); - - await this.workspaceCacheVersionService.incrementVersion(workspaceId); - - this.logger.log( - chalk.green(`Running command on workspace ${workspaceId} done`), - ); - } - - this.logger.log(chalk.green(`Command completed!`)); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.command.ts deleted file mode 100644 index f42b3fd39..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.command.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Command, CommandRunner, Option } from 'nest-commander'; - -import { AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand } from 'src/database/commands/upgrade-version/0-22/0-22-add-new-address-field-to-views-with-deprecated-address.command'; -import { FixObjectMetadataIdStandardIdCommand } from 'src/database/commands/upgrade-version/0-22/0-22-fix-object-metadata-id-standard-id.command'; -import { UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommand } from 'src/database/commands/upgrade-version/0-22/0-22-update-boolean-fields-null-default-values-and-null-values.command'; -import { UpdateMessageChannelSyncStageEnumCommand } from 'src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-stage-enum.command'; -import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-status-enum.command'; - -interface UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommandOptions { - workspaceId?: string; -} - -@Command({ - name: 'upgrade-0.22', - description: 'Upgrade to 0.22', -}) -export class UpgradeTo0_22Command extends CommandRunner { - constructor( - private readonly fixObjectMetadataIdStandardIdCommand: FixObjectMetadataIdStandardIdCommand, - private readonly updateBooleanFieldsNullDefaultValuesAndNullValuesCommand: UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommand, - private readonly addNewAddressFieldToViewsWithDeprecatedAddressFieldCommand: AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand, - private readonly updateMessageChannelSyncStatusEnumCommand: UpdateMessageChannelSyncStatusEnumCommand, - private readonly updateMessageChannelSyncStageEnumCommand: UpdateMessageChannelSyncStageEnumCommand, - ) { - 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: UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommandOptions, - ): Promise { - await this.fixObjectMetadataIdStandardIdCommand.run(_passedParam, options); - await this.updateBooleanFieldsNullDefaultValuesAndNullValuesCommand.run( - _passedParam, - options, - ); - await this.addNewAddressFieldToViewsWithDeprecatedAddressFieldCommand.run( - _passedParam, - options, - ); - await this.updateMessageChannelSyncStatusEnumCommand.run( - _passedParam, - options, - ); - await this.updateMessageChannelSyncStageEnumCommand.run( - _passedParam, - options, - ); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module.ts deleted file mode 100644 index 898f720bd..000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module'; -import { AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand } from 'src/database/commands/upgrade-version/0-22/0-22-add-new-address-field-to-views-with-deprecated-address.command'; -import { FixObjectMetadataIdStandardIdCommand } from 'src/database/commands/upgrade-version/0-22/0-22-fix-object-metadata-id-standard-id.command'; -import { UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommand } from 'src/database/commands/upgrade-version/0-22/0-22-update-boolean-fields-null-default-values-and-null-values.command'; -import { UpdateMessageChannelSyncStageEnumCommand } from 'src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-stage-enum.command'; -import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-22/0-22-update-message-channel-sync-status-enum.command'; -import { UpgradeTo0_22Command } from 'src/database/commands/upgrade-version/0-22/0-22-upgrade-version.command'; -import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; -import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; -import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; -import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module'; -import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; -import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; -import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module'; -import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; - -@Module({ - imports: [ - WorkspaceManagerModule, - DataSourceModule, - TypeORMModule, - TypeOrmModule.forFeature( - [Workspace, BillingSubscription, FeatureFlagEntity], - 'core', - ), - TypeOrmModule.forFeature( - [FieldMetadataEntity, ObjectMetadataEntity], - 'metadata', - ), - WorkspaceModule, - WorkspaceDataSourceModule, - WorkspaceSyncMetadataModule, - WorkspaceStatusModule, - ObjectMetadataModule, - DataSeedDemoWorkspaceModule, - WorkspaceCacheVersionModule, - ], - providers: [ - FixObjectMetadataIdStandardIdCommand, - UpdateBooleanFieldsNullDefaultValuesAndNullValuesCommand, - UpdateMessageChannelSyncStatusEnumCommand, - UpdateMessageChannelSyncStageEnumCommand, - AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand, - UpgradeTo0_22Command, - ], -}) -export class UpgradeTo0_22CommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command.ts index f70bb5fcc..3151a95cf 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command.ts @@ -183,9 +183,9 @@ export class MigrateLinkFieldsToLinksCommand extends CommandRunner { }); const viewFieldRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, - ViewFieldWorkspaceEntity, + 'viewField', ); const viewFieldsWithDeprecatedField = await viewFieldRepository.find({ diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts index 6b28e10a2..931da8141 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts @@ -8,8 +8,6 @@ import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decora import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; -import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; -import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { CalendarEventListFetchJob, @@ -40,8 +38,6 @@ import { export class GoogleAPIsService { constructor( private readonly twentyORMManager: TwentyORMManager, - @InjectWorkspaceDatasource() - private readonly workspaceDataSource: WorkspaceDataSource, @InjectMessageQueue(MessageQueue.messagingQueue) private readonly messageQueueService: MessageQueueService, @InjectMessageQueue(MessageQueue.calendarQueue) @@ -89,67 +85,67 @@ export class GoogleAPIsService { 'calendarChannel', ); - await this.workspaceDataSource.transaction( - async (manager: EntityManager) => { - if (!existingAccountId) { - await this.connectedAccountRepository.create( - { - id: newOrExistingConnectedAccountId, - handle, - provider: ConnectedAccountProvider.GOOGLE, - accessToken: input.accessToken, - refreshToken: input.refreshToken, - accountOwnerId: workspaceMemberId, - }, - workspaceId, - manager, - ); + const workspaceDataSource = await this.twentyORMManager.getDatasource(); - await this.messageChannelRepository.create( + await workspaceDataSource.transaction(async (manager: EntityManager) => { + if (!existingAccountId) { + await this.connectedAccountRepository.create( + { + id: newOrExistingConnectedAccountId, + handle, + provider: ConnectedAccountProvider.GOOGLE, + accessToken: input.accessToken, + refreshToken: input.refreshToken, + accountOwnerId: workspaceMemberId, + }, + workspaceId, + manager, + ); + + await this.messageChannelRepository.create( + { + id: v4(), + connectedAccountId: newOrExistingConnectedAccountId, + type: MessageChannelType.EMAIL, + handle, + visibility: + messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING, + syncStatus: MessageChannelSyncStatus.ONGOING, + }, + workspaceId, + manager, + ); + + if (isCalendarEnabled) { + await calendarChannelRepository.save( { id: v4(), connectedAccountId: newOrExistingConnectedAccountId, - type: MessageChannelType.EMAIL, handle, visibility: - messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING, - syncStatus: MessageChannelSyncStatus.ONGOING, + calendarVisibility || + CalendarChannelVisibility.SHARE_EVERYTHING, }, - workspaceId, - manager, - ); - - if (isCalendarEnabled) { - await calendarChannelRepository.save( - { - id: v4(), - connectedAccountId: newOrExistingConnectedAccountId, - handle, - visibility: - calendarVisibility || - CalendarChannelVisibility.SHARE_EVERYTHING, - }, - {}, - manager, - ); - } - } else { - await this.connectedAccountRepository.updateAccessTokenAndRefreshToken( - input.accessToken, - input.refreshToken, - newOrExistingConnectedAccountId, - workspaceId, - manager, - ); - - await this.messageChannelRepository.resetSync( - newOrExistingConnectedAccountId, - workspaceId, + {}, manager, ); } - }, - ); + } else { + await this.connectedAccountRepository.updateAccessTokenAndRefreshToken( + input.accessToken, + input.refreshToken, + newOrExistingConnectedAccountId, + workspaceId, + manager, + ); + + await this.messageChannelRepository.resetSync( + newOrExistingConnectedAccountId, + workspaceId, + manager, + ); + } + }); if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { const messageChannels = diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator.ts deleted file mode 100644 index 810dc1515..000000000 --- a/packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Inject } from '@nestjs/common'; - -import { TWENTY_ORM_WORKSPACE_DATASOURCE } from 'src/engine/twenty-orm/twenty-orm.constants'; - -// nit: The datasource can be null if it's used outside of an authenticated request context -export const InjectWorkspaceDatasource = () => - Inject(TWENTY_ORM_WORKSPACE_DATASOURCE); diff --git a/packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-repository.decorator.ts b/packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-repository.decorator.ts deleted file mode 100644 index 88050a06a..000000000 --- a/packages/twenty-server/src/engine/twenty-orm/decorators/inject-workspace-repository.decorator.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; - -import { getWorkspaceRepositoryToken } from 'src/engine/twenty-orm/utils/get-workspace-repository-token.util'; - -// nit: The repository can be null if it's used outside of an authenticated request context -export const InjectWorkspaceRepository = ( - entity: EntityClassOrSchema, -): ReturnType => Inject(getWorkspaceRepositoryToken(entity)); diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts index 0408e74c5..81d60b74b 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts @@ -1,10 +1,15 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { EntitySchema } from 'typeorm'; +import { Repository } from 'typeorm'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; +import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; +import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; +import { workspaceDataSourceCacheInstance } from 'src/engine/twenty-orm/twenty-orm-core.module'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; @Injectable() @@ -13,45 +18,111 @@ export class WorkspaceDatasourceFactory { private readonly dataSourceService: DataSourceService, private readonly environmentService: EnvironmentService, private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, + private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, + @InjectRepository(ObjectMetadataEntity, 'metadata') + private readonly objectMetadataRepository: Repository, + private readonly entitySchemaFactory: EntitySchemaFactory, ) {} public async create( - entities: EntitySchema[], workspaceId: string, - ): Promise { - const dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( - workspaceId, - ); + cacheVersion: string | null, + ): Promise { + let dataSourceCacheVersion: string; - if (!dataSourceMetadata) { - return null; + if (cacheVersion) { + dataSourceCacheVersion = cacheVersion; + } else { + const cacheVersionFromDatabase = + await this.workspaceCacheVersionService.getVersion(workspaceId); + + if (!cacheVersionFromDatabase) { + throw new Error('Cache version not found'); + } + + dataSourceCacheVersion = cacheVersionFromDatabase; } - const workspaceDataSource = new WorkspaceDataSource( - { - workspaceId, - workspaceCacheStorage: this.workspaceCacheStorageService, - }, - { - url: - dataSourceMetadata.url ?? - this.environmentService.get('PG_DATABASE_URL'), - type: 'postgres', - logging: this.environmentService.get('DEBUG_MODE') - ? ['query', 'error'] - : ['error'], - schema: dataSourceMetadata.schema, - entities, - ssl: this.environmentService.get('PG_SSL_ALLOW_SELF_SIGNED') - ? { - rejectUnauthorized: false, - } - : undefined, + const workspaceDataSource = await workspaceDataSourceCacheInstance.execute( + `${workspaceId}-${dataSourceCacheVersion}`, + async () => { + const dataSourceMetadata = + await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( + workspaceId, + ); + + if (!dataSourceMetadata) { + throw new Error('Data source metadata not found'); + } + + const latestCacheVersion = + await this.workspaceCacheVersionService.getVersion(workspaceId); + + if (latestCacheVersion !== dataSourceCacheVersion) { + throw new Error('Cache version mismatch'); + } + + let objectMetadataCollection = + await this.workspaceCacheStorageService.getObjectMetadataCollection( + workspaceId, + ); + + if (!objectMetadataCollection) { + objectMetadataCollection = await this.objectMetadataRepository.find({ + where: { workspaceId }, + relations: [ + 'fields.object', + 'fields', + 'fields.fromRelationMetadata', + 'fields.toRelationMetadata', + 'fields.fromRelationMetadata.toObjectMetadata', + ], + }); + + await this.workspaceCacheStorageService.setObjectMetadataCollection( + workspaceId, + objectMetadataCollection, + ); + } + + const entities = await Promise.all( + objectMetadataCollection.map((objectMetadata) => + this.entitySchemaFactory.create(workspaceId, objectMetadata), + ), + ); + const workspaceDataSource = new WorkspaceDataSource( + { + workspaceId, + workspaceCacheStorage: this.workspaceCacheStorageService, + }, + { + url: + dataSourceMetadata.url ?? + this.environmentService.get('PG_DATABASE_URL'), + type: 'postgres', + logging: this.environmentService.get('DEBUG_MODE') + ? ['query', 'error'] + : ['error'], + schema: dataSourceMetadata.schema, + entities, + ssl: this.environmentService.get('PG_SSL_ALLOW_SELF_SIGNED') + ? { + rejectUnauthorized: false, + } + : undefined, + }, + ); + + await workspaceDataSource.initialize(); + + return workspaceDataSource; }, + (dataSource) => dataSource.destroy(), ); - await workspaceDataSource.initialize(); + if (!workspaceDataSource) { + throw new Error('Workspace data source not found'); + } return workspaceDataSource; } diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm-core.module.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm-core.module.ts index dc6540a84..ebb929a64 100644 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm-core.module.ts +++ b/packages/twenty-server/src/engine/twenty-orm/twenty-orm-core.module.ts @@ -4,11 +4,8 @@ import { Logger, Module, OnApplicationShutdown, - Provider, } from '@nestjs/common'; -import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { TwentyORMModuleAsyncOptions, @@ -22,18 +19,11 @@ import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/l import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; import { entitySchemaFactories } from 'src/engine/twenty-orm/factories'; import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; -import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory'; import { CacheManager } from 'src/engine/twenty-orm/storage/cache-manager.storage'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { TWENTY_ORM_WORKSPACE_DATASOURCE } from 'src/engine/twenty-orm/twenty-orm.constants'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { - ConfigurableModuleClass, - MODULE_OPTIONS_TOKEN, -} from 'src/engine/twenty-orm/twenty-orm.module-definition'; +import { ConfigurableModuleClass } from 'src/engine/twenty-orm/twenty-orm.module-definition'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; export const workspaceDataSourceCacheInstance = new CacheManager(); @@ -68,28 +58,10 @@ export class TwentyORMCoreModule static register(options: TwentyORMOptions): DynamicModule { const dynamicModule = super.register(options); - // TODO: Avoid code duplication here - const providers: Provider[] = [ - { - provide: TWENTY_ORM_WORKSPACE_DATASOURCE, - useFactory: this.createWorkspaceDataSource, - inject: [ - WorkspaceCacheStorageService, - getRepositoryToken(ObjectMetadataEntity, 'metadata'), - EntitySchemaFactory, - ScopedWorkspaceContextFactory, - WorkspaceDatasourceFactory, - ], - }, - ]; - return { ...dynamicModule, - providers: [...(dynamicModule.providers ?? []), ...providers], - exports: [ - ...(dynamicModule.exports ?? []), - TWENTY_ORM_WORKSPACE_DATASOURCE, - ], + providers: [...(dynamicModule.providers ?? [])], + exports: [...(dynamicModule.exports ?? [])], }; } @@ -97,89 +69,14 @@ export class TwentyORMCoreModule asyncOptions: TwentyORMModuleAsyncOptions, ): DynamicModule { const dynamicModule = super.registerAsync(asyncOptions); - const providers: Provider[] = [ - { - provide: TWENTY_ORM_WORKSPACE_DATASOURCE, - useFactory: this.createWorkspaceDataSource, - inject: [ - WorkspaceCacheStorageService, - getRepositoryToken(ObjectMetadataEntity, 'metadata'), - EntitySchemaFactory, - ScopedWorkspaceContextFactory, - WorkspaceDatasourceFactory, - MODULE_OPTIONS_TOKEN, - ], - }, - ]; return { ...dynamicModule, - providers: [...(dynamicModule.providers ?? []), ...providers], - exports: [ - ...(dynamicModule.exports ?? []), - TWENTY_ORM_WORKSPACE_DATASOURCE, - ], + providers: [...(dynamicModule.providers ?? [])], + exports: [...(dynamicModule.exports ?? [])], }; } - static async createWorkspaceDataSource( - workspaceCacheStorageService: WorkspaceCacheStorageService, - objectMetadataRepository: Repository, - entitySchemaFactory: EntitySchemaFactory, - scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, - workspaceDataSourceFactory: WorkspaceDatasourceFactory, - _options?: TwentyORMOptions, - ) { - const { workspaceId, cacheVersion } = - scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - return null; - } - - return workspaceDataSourceCacheInstance.execute( - `${workspaceId}-${cacheVersion}`, - async () => { - let objectMetadataCollection = - await workspaceCacheStorageService.getObjectMetadataCollection( - workspaceId, - ); - - if (!objectMetadataCollection) { - objectMetadataCollection = await objectMetadataRepository.find({ - where: { workspaceId }, - relations: [ - 'fields.object', - 'fields', - 'fields.fromRelationMetadata', - 'fields.toRelationMetadata', - 'fields.fromRelationMetadata.toObjectMetadata', - ], - }); - - await workspaceCacheStorageService.setObjectMetadataCollection( - workspaceId, - objectMetadataCollection, - ); - } - - const entities = await Promise.all( - objectMetadataCollection.map((objectMetadata) => - entitySchemaFactory.create(workspaceId, objectMetadata), - ), - ); - - const workspaceDataSource = await workspaceDataSourceFactory.create( - entities, - workspaceId, - ); - - return workspaceDataSource; - }, - (dataSource) => dataSource.destroy(), - ); - } - /** * Destroys all data sources on application shutdown */ diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm-global.manager.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm-global.manager.ts index 1fc1f0b04..d03750ff3 100644 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm-global.manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/twenty-orm-global.manager.ts @@ -1,114 +1,29 @@ -import { Injectable, Type } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common'; -import { ObjectLiteral, Repository } from 'typeorm'; +import { ObjectLiteral } from 'typeorm'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; -import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity'; -import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { workspaceDataSourceCacheInstance } from 'src/engine/twenty-orm/twenty-orm-core.module'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; -import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; @Injectable() export class TwentyORMGlobalManager { constructor( - private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly workspaceDataSourceFactory: WorkspaceDatasourceFactory, - private readonly entitySchemaFactory: EntitySchemaFactory, ) {} async getRepositoryForWorkspace( - workspaceId: string, - entityClass: Type, - ): Promise>; - - async getRepositoryForWorkspace( workspaceId: string, objectMetadataName: string, - ): Promise>; - - async getRepositoryForWorkspace( - workspaceId: string, - entityClassOrobjectMetadataName: Type | string, - ): Promise< - WorkspaceRepository | WorkspaceRepository - > { - let objectMetadataName: string; - - if (typeof entityClassOrobjectMetadataName === 'string') { - objectMetadataName = entityClassOrobjectMetadataName; - } else { - objectMetadataName = convertClassNameToObjectMetadataName( - entityClassOrobjectMetadataName.name, - ); - } - - return this.buildRepositoryForWorkspace(workspaceId, objectMetadataName); - } - - async buildDatasourceForWorkspace(workspaceId: string) { - const cacheVersion = - await this.workspaceCacheVersionService.getVersion(workspaceId); - - let objectMetadataCollection = - await this.workspaceCacheStorageService.getObjectMetadataCollection( - workspaceId, - ); - - if (!objectMetadataCollection) { - objectMetadataCollection = await this.objectMetadataRepository.find({ - where: { workspaceId }, - relations: [ - 'fields.object', - 'fields', - 'fields.fromRelationMetadata', - 'fields.toRelationMetadata', - 'fields.fromRelationMetadata.toObjectMetadata', - ], - }); - - await this.workspaceCacheStorageService.setObjectMetadataCollection( - workspaceId, - objectMetadataCollection, - ); - } - - const entities = await Promise.all( - objectMetadataCollection.map((objectMetadata) => - this.entitySchemaFactory.create(workspaceId, objectMetadata), - ), + ): Promise> { + const workspaceDataSource = await this.workspaceDataSourceFactory.create( + workspaceId, + null, ); - return await workspaceDataSourceCacheInstance.execute( - `${workspaceId}-${cacheVersion}`, - async () => { - const workspaceDataSource = - await this.workspaceDataSourceFactory.create(entities, workspaceId); - - return workspaceDataSource; - }, - (dataSource) => dataSource.destroy(), - ); - } - - async buildRepositoryForWorkspace( - workspaceId: string, - objectMetadataName: string, - ) { - const workspaceDataSource = - await this.buildDatasourceForWorkspace(workspaceId); - - if (!workspaceDataSource) { - throw new Error('Workspace data source not found'); - } - return workspaceDataSource.getRepository(objectMetadataName); } + + async getDataSourceForWorkspace(workspaceId: string) { + return this.workspaceDataSourceFactory.create(workspaceId, null); + } } diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.constants.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.constants.ts deleted file mode 100644 index 749829090..000000000 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const TWENTY_ORM_WORKSPACE_DATASOURCE = - 'TWENTY_ORM_WORKSPACE_DATASOURCE'; diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts index 3fb9a0bb1..d04c9030a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts @@ -1,159 +1,44 @@ -import { Injectable, Optional, Type } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common'; -import { ObjectLiteral, Repository } from 'typeorm'; +import { ObjectLiteral } from 'typeorm'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; -import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity'; -import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; -import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator'; -import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; +import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { workspaceDataSourceCacheInstance } from 'src/engine/twenty-orm/twenty-orm-core.module'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; -import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; @Injectable() export class TwentyORMManager { constructor( - @Optional() - @InjectWorkspaceDatasource() - private readonly workspaceDataSource: WorkspaceDataSource | null, - private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly workspaceDataSourceFactory: WorkspaceDatasourceFactory, - private readonly entitySchemaFactory: EntitySchemaFactory, + private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, ) {} async getRepository( objectMetadataName: string, - ): Promise>; - - async getRepository( - entityClass: Type, - ): Promise>; - - async getRepository( - entityClassOrobjectMetadataName: Type | string, ): Promise> { - let objectMetadataName: string; + const { workspaceId, cacheVersion } = + this.scopedWorkspaceContextFactory.create(); - if (typeof entityClassOrobjectMetadataName === 'string') { - objectMetadataName = entityClassOrobjectMetadataName; - } else { - objectMetadataName = convertClassNameToObjectMetadataName( - entityClassOrobjectMetadataName.name, - ); + if (!workspaceId) { + throw new Error('Workspace not found'); } - if (!this.workspaceDataSource) { - throw new Error('Workspace data source not found'); - } - - const workspaceId = this.workspaceDataSource.getWorkspaceId(); - - return this.buildRepositoryForWorkspace(workspaceId, objectMetadataName); - } - - async getRepositoryForWorkspace( - workspaceId: string, - entityClass: Type, - ): Promise>; - - async getRepositoryForWorkspace( - workspaceId: string, - objectMetadataName: string, - ): Promise>; - - async getRepositoryForWorkspace( - workspaceId: string, - entityClassOrobjectMetadataName: Type | string, - ): Promise< - WorkspaceRepository | WorkspaceRepository - > { - let objectMetadataName: string; - - if (typeof entityClassOrobjectMetadataName === 'string') { - objectMetadataName = entityClassOrobjectMetadataName; - } else { - objectMetadataName = convertClassNameToObjectMetadataName( - entityClassOrobjectMetadataName.name, - ); - } - - return this.buildRepositoryForWorkspace(workspaceId, objectMetadataName); - } - - async getWorkspaceDatasource() { - if (!this.workspaceDataSource) { - throw new Error('Workspace data source not found'); - } - - const workspaceId = this.workspaceDataSource.getWorkspaceId(); - - return this.buildDatasourceForWorkspace(workspaceId); - } - - async buildDatasourceForWorkspace(workspaceId: string) { - const cacheVersion = - await this.workspaceCacheVersionService.getVersion(workspaceId); - - let objectMetadataCollection = - await this.workspaceCacheStorageService.getObjectMetadataCollection( - workspaceId, - ); - - if (!objectMetadataCollection) { - objectMetadataCollection = await this.objectMetadataRepository.find({ - where: { workspaceId }, - relations: [ - 'fields.object', - 'fields', - 'fields.fromRelationMetadata', - 'fields.toRelationMetadata', - 'fields.fromRelationMetadata.toObjectMetadata', - ], - }); - - await this.workspaceCacheStorageService.setObjectMetadataCollection( - workspaceId, - objectMetadataCollection, - ); - } - - const entities = await Promise.all( - objectMetadataCollection.map((objectMetadata) => - this.entitySchemaFactory.create(workspaceId, objectMetadata), - ), + const workspaceDataSource = await this.workspaceDataSourceFactory.create( + workspaceId, + cacheVersion, ); - return await workspaceDataSourceCacheInstance.execute( - `${workspaceId}-${cacheVersion}`, - async () => { - const workspaceDataSource = - await this.workspaceDataSourceFactory.create(entities, workspaceId); - - return workspaceDataSource; - }, - (dataSource) => dataSource.destroy(), - ); - } - - async buildRepositoryForWorkspace( - workspaceId: string, - objectMetadataName: string, - ) { - const workspaceDataSource = - await this.buildDatasourceForWorkspace(workspaceId); - - if (!workspaceDataSource) { - throw new Error('Workspace data source not found'); - } - return workspaceDataSource.getRepository(objectMetadataName); } + + async getDatasource() { + const { workspaceId, cacheVersion } = + this.scopedWorkspaceContextFactory.create(); + + if (!workspaceId) { + throw new Error('Workspace not found'); + } + + return this.workspaceDataSourceFactory.create(workspaceId, cacheVersion); + } } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/get-workspace-repository-token.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/get-workspace-repository-token.util.ts deleted file mode 100644 index ad9a61537..000000000 --- a/packages/twenty-server/src/engine/twenty-orm/utils/get-workspace-repository-token.util.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type'; - -import { EntitySchema, Repository } from 'typeorm'; - -export function getWorkspaceRepositoryToken( - entity: EntityClassOrSchema, - // eslint-disable-next-line @typescript-eslint/ban-types -): Function | string { - if (entity === null || entity === undefined) { - throw new Error('Circular dependency @InjectWorkspaceRepository()'); - } - - if (entity instanceof Function && entity.prototype instanceof Repository) { - return entity; - } - - if (entity instanceof EntitySchema) { - return `${ - entity.options.target ? entity.options.target.name : entity.options.name - }WorkspaceRepository`; - } - - return `${entity.name}WorkspaceRepository`; -} diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts index d8d35d128..a9501ad2f 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts @@ -44,9 +44,10 @@ export class BlocklistReimportCalendarEventsJob { return; } - const calendarChannelRepository = await this.twentyORMManager.getRepository( - CalendarChannelWorkspaceEntity, - ); + const calendarChannelRepository = + await this.twentyORMManager.getRepository( + 'calendarChannel', + ); await calendarChannelRepository.update( { diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts index 30c6fad2e..09fdc599a 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts @@ -9,15 +9,12 @@ import { Processor } from 'src/engine/integrations/message-queue/decorators/proc import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarEventListFetchJob, CalendarEventsImportJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; -import { - CalendarChannelSyncStage, - CalendarChannelWorkspaceEntity, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; @Processor({ queueName: MessageQueue.cronQueue, @@ -29,7 +26,7 @@ export class CalendarEventListFetchCronJob { @InjectMessageQueue(MessageQueue.calendarQueue) private readonly messageQueueService: MessageQueueService, private readonly billingService: BillingService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} @Process(CalendarEventListFetchCronJob.name) @@ -49,9 +46,9 @@ export class CalendarEventListFetchCronJob { for (const workspaceId of workspaceIdsWithDataSources) { const calendarChannelRepository = - await this.twentyORMManager.getRepositoryForWorkspace( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, - CalendarChannelWorkspaceEntity, + 'calendarChannel', ); const calendarChannels = await calendarChannelRepository.find({ diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts index 1dde06ef4..ad0f4e11d 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts @@ -6,8 +6,6 @@ import { Any } from 'typeorm'; import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; -import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; -import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { injectIdsInCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/utils/inject-ids-in-calendar-events.util'; import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service'; @@ -26,8 +24,6 @@ import { export class CalendarSaveEventsService { constructor( private readonly twentyORMManager: TwentyORMManager, - @InjectWorkspaceDatasource() - private readonly workspaceDataSource: WorkspaceDataSource, private readonly calendarEventParticipantService: CalendarEventParticipantService, @InjectMessageQueue(MessageQueue.contactCreationQueue) private readonly messageQueueService: MessageQueueService, @@ -119,8 +115,7 @@ export class CalendarSaveEventsService { const savedCalendarEventParticipantsToEmit: CalendarEventParticipantWorkspaceEntity[] = []; - const workspaceDataSource = - await this.twentyORMManager.getWorkspaceDatasource(); + const workspaceDataSource = await this.twentyORMManager.getDatasource(); await workspaceDataSource?.transaction(async (transactionManager) => { await calendarEventRepository.save(eventsToSave, {}, transactionManager); diff --git a/packages/twenty-server/src/modules/view/services/view.service.ts b/packages/twenty-server/src/modules/view/services/view.service.ts index db6b02bb6..4228ea127 100644 --- a/packages/twenty-server/src/modules/view/services/view.service.ts +++ b/packages/twenty-server/src/modules/view/services/view.service.ts @@ -4,7 +4,6 @@ import { isDefined } from 'class-validator'; import isEmpty from 'lodash.isempty'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; @Injectable() export class ViewService { @@ -29,7 +28,7 @@ export class ViewService { const viewFieldRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, - ViewFieldWorkspaceEntity, + 'viewField', ); for (const viewId of viewsIds) { @@ -71,7 +70,7 @@ export class ViewService { const viewFieldRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, - ViewFieldWorkspaceEntity, + 'viewField', ); const viewsWithField = await viewFieldRepository.find({ where: {