diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index c1e1d8111..66a879a74 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -3,58 +3,59 @@ import { InjectRepository } from '@nestjs/typeorm'; import console from 'console'; -import { FindManyOptions, FindOneOptions, Repository } from 'typeorm'; -import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { Query, QueryOptions } from '@ptc-org/nestjs-query-core'; +import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; +import { FindManyOptions, FindOneOptions, Repository } from 'typeorm'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; -import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; -import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; -import { - WorkspaceMigrationColumnActionType, - WorkspaceMigrationColumnDrop, - WorkspaceMigrationTableActionType, -} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { TypeORMService } from 'src/database/typeorm/typeorm.service'; +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 { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; +import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; +import { + ObjectMetadataException, + ObjectMetadataExceptionCode, +} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { buildMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util'; +import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { RelationMetadataEntity, RelationMetadataType, RelationOnDeleteAction, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; -import { computeTableName } from 'src/engine/utils/compute-table-name.util'; -import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; -import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { RelationToDelete } from 'src/engine/metadata-modules/relation-metadata/types/relation-to-delete'; +import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; +import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; +import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationColumnDrop, + WorkspaceMigrationTableActionType, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { ACTIVITY_TARGET_STANDARD_FIELD_IDS, ATTACHMENT_STANDARD_FIELD_IDS, BASE_OBJECT_STANDARD_FIELD_IDS, CUSTOM_OBJECT_STANDARD_FIELD_IDS, FAVORITE_STANDARD_FIELD_IDS, + NOTE_TARGET_STANDARD_FIELD_IDS, TIMELINE_ACTIVITY_STANDARD_FIELD_IDS, } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { createForeignKeyDeterministicUuid, createRelationDeterministicUuid, } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util'; -import { buildMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util'; -import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; -import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; -import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; -import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; -import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; -import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; -import { - ObjectMetadataException, - ObjectMetadataExceptionCode, -} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; import { ObjectMetadataEntity } from './object-metadata.entity'; @@ -477,6 +478,20 @@ export class ObjectMetadataService extends TypeOrmQueryService + | undefined, + ) { + const noteTargetObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'noteTarget', + workspaceId: workspaceId, + }); + + await this.fieldMetadataRepository.save( + // Foreign key + { + standardId: createForeignKeyDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: noteTargetObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + description: `NoteTarget ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + }, + ); + + const noteTargetRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM + { + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.noteTargets, + objectMetadataId: createdObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: 'noteTargets', + label: 'Notes', + description: `Notes tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconCheckbox', + isNullable: true, + }, + // TO + { + standardId: createRelationDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: noteTargetObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + description: `NoteTarget ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, + }, + ]); + + const noteTargetRelationFieldMetadataMap = + noteTargetRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: noteTargetObjectMetadata.id, + fromFieldMetadataId: + noteTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + noteTargetRelationFieldMetadataMap[noteTargetObjectMetadata.id].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, + }, + ]); + + return { noteTargetObjectMetadata }; + } + + private async createTaskTargetRelation( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + objectPrimaryKeyType: FieldMetadataType, + objectPrimaryKeyFieldSettings: + | FieldMetadataSettings + | undefined, + ) { + const taskTargetObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'taskTarget', + workspaceId: workspaceId, + }); + + await this.fieldMetadataRepository.save( + // Foreign key + { + standardId: createForeignKeyDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: taskTargetObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: objectPrimaryKeyType, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + description: `TaskTarget ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true }, + }, + ); + + const taskTargetRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM + { + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.taskTargets, + objectMetadataId: createdObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: 'taskTargets', + label: 'Tasks', + description: `Tasks tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconCheckbox', + isNullable: true, + }, + // TO + { + standardId: createRelationDeterministicUuid({ + objectId: createdObjectMetadata.id, + standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom, + }), + objectMetadataId: taskTargetObjectMetadata.id, + workspaceId: workspaceId, + isCustom: false, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + description: `TaskTarget ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, + }, + ]); + + const taskTargetRelationFieldMetadataMap = + taskTargetRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: taskTargetObjectMetadata.id, + fromFieldMetadataId: + taskTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + taskTargetRelationFieldMetadataMap[taskTargetObjectMetadata.id].id, + onDeleteAction: RelationOnDeleteAction.CASCADE, + }, + ]); + + return { taskTargetObjectMetadata }; + } + private async createAttachmentRelation( workspaceId: string, createdObjectMetadata: ObjectMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts index 653a53178..16a44b460 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util.ts @@ -2,9 +2,9 @@ import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/ut import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { - WorkspaceMigrationTableAction, WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnCreate, + WorkspaceMigrationTableAction, WorkspaceMigrationTableActionType, } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -15,6 +15,8 @@ export const buildMigrationsForCustomObjectRelations = ( attachmentObjectMetadata: ObjectMetadataEntity, timelineActivityObjectMetadata: ObjectMetadataEntity, favoriteObjectMetadata: ObjectMetadataEntity, + noteTargetObjectMetadata: ObjectMetadataEntity, + taskTargetObjectMetadata: ObjectMetadataEntity, ): WorkspaceMigrationTableAction[] => [ { name: computeObjectTargetTable(createdObjectMetadata), @@ -50,6 +52,66 @@ export const buildMigrationsForCustomObjectRelations = ( }, ], }, + // Add note target relation + { + name: computeObjectTargetTable(noteTargetObjectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.CREATE, + columnName: computeColumnName(createdObjectMetadata.nameSingular, { + isForeignKey: true, + }), + columnType: 'uuid', + isNullable: true, + } satisfies WorkspaceMigrationColumnCreate, + ], + }, + { + name: computeObjectTargetTable(noteTargetObjectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, + columnName: computeColumnName(createdObjectMetadata.nameSingular, { + isForeignKey: true, + }), + referencedTableName: computeObjectTargetTable(createdObjectMetadata), + referencedTableColumnName: 'id', + onDelete: RelationOnDeleteAction.CASCADE, + }, + ], + }, + // Add task target relation + { + name: computeObjectTargetTable(taskTargetObjectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.CREATE, + columnName: computeColumnName(createdObjectMetadata.nameSingular, { + isForeignKey: true, + }), + columnType: 'uuid', + isNullable: true, + } satisfies WorkspaceMigrationColumnCreate, + ], + }, + { + name: computeObjectTargetTable(taskTargetObjectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: [ + { + action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY, + columnName: computeColumnName(createdObjectMetadata.nameSingular, { + isForeignKey: true, + }), + referencedTableName: computeObjectTargetTable(createdObjectMetadata), + referencedTableColumnName: 'id', + onDelete: RelationOnDeleteAction.CASCADE, + }, + ], + }, // Add attachment relation { name: computeObjectTargetTable(attachmentObjectMetadata), diff --git a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts index 9c58ff333..d552ffbd4 100644 --- a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts +++ b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts @@ -143,8 +143,9 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceRelation({ standardId: PERSON_STANDARD_FIELD_IDS.pointOfContactForOpportunities, type: RelationMetadataType.ONE_TO_MANY, - label: 'POC for Opportunities', - description: 'Point of Contact for Opportunities', + label: 'Linked Opportunities', + description: + 'List of opportunities for which that person is the point of contact', icon: 'IconTargetArrow', inverseSideTarget: () => OpportunityWorkspaceEntity, inverseSideFieldKey: 'pointOfContact',