import { InjectRepository } from '@nestjs/typeorm'; import chalk from 'chalk'; import { Command } from 'nest-commander'; import { FieldMetadataType } from 'twenty-shared/types'; import { In, Repository } from 'typeorm'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { ActiveOrSuspendedWorkspacesMigrationCommandRunner, RunOnWorkspaceArgs, } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { RelationDirection, deduceRelationDirection, } from 'src/engine/utils/deduce-relation-direction.util'; import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; @Command({ name: 'upgrade:0-51:migrate-relations-to-field-metadata', description: 'Migrate relations to field metadata', }) export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, @InjectRepository(FieldMetadataEntity, 'metadata') private readonly fieldMetadataRepository: Repository, protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) { super(workspaceRepository, twentyORMGlobalManager); } override async runOnWorkspace({ index, total, workspaceId, }: RunOnWorkspaceArgs): Promise { this.logger.log( `Running command for workspace ${workspaceId} ${index + 1}/${total}`, ); const fieldMetadataCollection = await this.fieldMetadataRepository.find({ where: { workspaceId, type: In([FieldMetadataType.RELATION, FieldMetadataType.UUID]), }, relations: ['fromRelationMetadata', 'toRelationMetadata'], }); if (!fieldMetadataCollection.length) { this.logger.log( chalk.yellow( `No relation field metadata found for workspace ${workspaceId}.`, ), ); return; } const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter( (fieldMetadata) => isFieldMetadataOfType(fieldMetadata, FieldMetadataType.UUID), // TODO: Fix this, it's working in other places but not here ) as FieldMetadataEntity[]; const fieldMetadataToUpdateCollection = fieldMetadataCollection .filter((fieldMetadata) => isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION), ) .map((fieldMetadata) => this.updateRelationFieldMetadata( joinColumnFieldMetadataCollection, // TODO: Fix this, it's working in other places but not here fieldMetadata as FieldMetadataEntity, ), ); if (fieldMetadataToUpdateCollection.length > 0) { await this.fieldMetadataRepository.save(fieldMetadataToUpdateCollection); } this.logger.log( chalk.green(`Command completed for workspace ${workspaceId}.`), ); } private updateRelationFieldMetadata( joinColumnFieldMetadataCollection: FieldMetadataEntity[], fieldMetadata: FieldMetadataEntity, ): FieldMetadataEntity { const relationMetadata = fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata; const joinColumnFieldMetadata = joinColumnFieldMetadataCollection.find( (joinColumnFieldMetadata) => // We're deducing the field based on the name of the relation field // This is not the best way to do this but we don't have a better way joinColumnFieldMetadata.name === `${fieldMetadata.name}Id`, ); const relationDirection = deduceRelationDirection( fieldMetadata, relationMetadata, ); let relationType = relationMetadata.relationType as unknown as RelationType; if ( relationDirection === RelationDirection.TO && relationType === RelationType.ONE_TO_MANY ) { relationType = RelationType.MANY_TO_ONE; } const relationTargetFieldMetadataId = relationDirection === RelationDirection.FROM ? relationMetadata.toFieldMetadataId : relationMetadata.fromFieldMetadataId; const relationTargetObjectMetadataId = relationDirection === RelationDirection.FROM ? relationMetadata.toObjectMetadataId : relationMetadata.fromObjectMetadataId; return { ...fieldMetadata, settings: { relationType, onDelete: relationMetadata.onDeleteAction, joinColumnName: joinColumnFieldMetadata?.name, }, relationTargetFieldMetadataId, relationTargetObjectMetadataId, }; } }