From 5efc2f00b9006423fddae873b3499f2a90f7893e Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 7 Dec 2023 19:22:34 +0100 Subject: [PATCH] Sync metadata generate migrations (#2864) * Sync Metadata generates migrations * add execute migrations * fix relations + add isActive on creation * fix composite fields migration * remove dependency * use new metadata setup for seed-dev * fix rebase * remove unused code * fix viewField dev seeds * fix isSystem --- server/src/command.module.ts | 4 +- .../data-seed-dev-workspace.command.ts | 56 ++- .../commands/database-command.module.ts | 8 +- .../metadata/field-metadata/view-filter.ts | 10 +- .../typeorm-seeds/workspace/view-fields.ts | 169 ------- .../database/typeorm-seeds/workspace/views.ts | 228 +++++++-- server/src/database/typeorm/typeorm.module.ts | 2 + .../currency.composite-type.ts | 36 +- .../full-name.composite-type.ts | 32 +- .../composite-types/link.composite-type.ts | 32 +- .../utils/generate-default-value.ts | 23 + .../workspace-migration.factory.ts | 34 +- .../decorators/metadata.decorator.ts | 103 ----- .../standard-objects/activity-target.ts | 90 ---- .../standard-objects/activity.ts | 181 -------- .../standard-objects/api-key.ts | 57 --- .../standard-objects/attachment.ts | 124 ----- .../standard-objects/comment.ts | 78 ---- .../standard-objects/company.ts | 212 --------- .../standard-objects/favorite.ts | 104 ----- .../standard-objects/opportunity.ts | 165 ------- .../standard-objects/person.ts | 209 --------- .../standard-objects/pipeline-step.ts | 71 --- .../standard-objects/relations/activity.ts | 27 -- .../standard-objects/relations/company.ts | 41 -- .../standard-objects/relations/person.ts | 41 -- .../relations/pipeline-step.ts | 13 - .../standard-objects/relations/view.ts | 27 -- .../relations/workspace-member.ts | 48 -- .../standard-object-metadata.ts | 82 ---- .../standard-object-relation-metadata.ts | 15 - .../standard-objects/view-field.ts | 96 ---- .../standard-objects/view-filter.ts | 96 ---- .../standard-objects/view-sort.ts | 68 --- .../standard-objects/view.ts | 85 ---- .../standard-objects/webhook.ts | 45 -- .../standard-objects/workspace-member.ts | 156 ------- .../utils/metadata.parser.ts | 42 -- .../workspace-manager.module.ts | 13 +- .../workspace-manager.service.ts | 369 +-------------- .../sync-workspace-metadata.command.ts | 8 +- ...orkspace-sync-metadata-commands.module.ts} | 6 +- .../decorators/metadata.decorator.ts | 183 ++++++++ .../activity-target.object-metadata.ts | 47 ++ .../activity.object-metadata.ts | 128 ++++++ .../api-key.object-metadata.ts | 8 +- .../attachment.object-metadata.ts | 80 ++++ .../standard-objects/base.object-metadata.ts | 3 +- .../comment.object-metadata.ts | 44 ++ .../company.object-metadata.ts | 164 +++++++ .../favorite.object-metadata.ts | 54 +++ .../standard-objects/index.ts | 35 ++ .../opportunity.object-metadata.ts | 85 ++++ .../person.object-metadata.ts | 164 +++++++ .../pipeline-step.object-metadata.ts | 62 +++ .../view-field.object-metadata.ts | 63 +++ .../view-filter.object-metadata.ts | 63 +++ .../view-sort.object-metadata.ts | 19 +- .../standard-objects/view.object-metadata.ts | 81 ++++ .../webook.object-metadata.ts | 8 +- .../workspace-member.object-metadata.ts | 142 ++++++ .../utils/metadata.parser.ts | 109 +++++ .../utils/sync-metadata.util.spec.ts | 0 .../utils/sync-metadata.util.ts | 24 +- .../worksapce-sync-metadata.module.ts | 29 ++ .../workspace-sync.metadata.service.ts | 435 ++++++++++++++++++ 66 files changed, 2393 insertions(+), 2943 deletions(-) delete mode 100644 server/src/database/typeorm-seeds/workspace/view-fields.ts create mode 100644 server/src/metadata/field-metadata/utils/generate-default-value.ts delete mode 100644 server/src/workspace/workspace-manager/decorators/metadata.decorator.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/activity-target.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/activity.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/api-key.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/attachment.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/comment.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/company.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/favorite.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/opportunity.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/person.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/pipeline-step.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/relations/activity.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/relations/company.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/relations/person.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/relations/pipeline-step.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/relations/view.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/relations/workspace-member.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/standard-object-relation-metadata.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/view-field.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/view-filter.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/view-sort.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/view.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/webhook.ts delete mode 100644 server/src/workspace/workspace-manager/standard-objects/workspace-member.ts delete mode 100644 server/src/workspace/workspace-manager/utils/metadata.parser.ts rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/commands/sync-workspace-metadata.command.ts (73%) rename server/src/workspace/{workspace-manager/commands/workspace-manager-commands.module.ts => workspace-sync-metadata/commands/workspace-sync-metadata-commands.module.ts} (53%) create mode 100644 server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata.ts rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/standard-objects/api-key.object-metadata.ts (80%) create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata.ts rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/standard-objects/base.object-metadata.ts (89%) create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/index.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/pipeline-step.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/view-field.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/view-filter.object-metadata.ts rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/standard-objects/view-sort.object-metadata.ts (63%) create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata.ts rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/standard-objects/webook.object-metadata.ts (80%) create mode 100644 server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts create mode 100644 server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/utils/sync-metadata.util.spec.ts (100%) rename server/src/workspace/{workspace-manager => workspace-sync-metadata}/utils/sync-metadata.util.ts (64%) create mode 100644 server/src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module.ts create mode 100644 server/src/workspace/workspace-sync-metadata/workspace-sync.metadata.service.ts diff --git a/server/src/command.module.ts b/server/src/command.module.ts index 6b64f2148..01068663d 100644 --- a/server/src/command.module.ts +++ b/server/src/command.module.ts @@ -4,14 +4,14 @@ import { DatabaseCommandModule } from 'src/database/commands/database-command.mo import { AppModule } from './app.module'; -import { WorkspaceManagerCommandsModule } from './workspace/workspace-manager/commands/workspace-manager-commands.module'; +import { WorkspaceSyncMetadataCommandsModule } from './workspace/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; import { WorkspaceMigrationRunnerCommandsModule } from './workspace/workspace-migration-runner/commands/workspace-migration-runner-commands.module'; @Module({ imports: [ AppModule, WorkspaceMigrationRunnerCommandsModule, - WorkspaceManagerCommandsModule, + WorkspaceSyncMetadataCommandsModule, DatabaseCommandModule, ], }) diff --git a/server/src/database/commands/data-seed-dev-workspace.command.ts b/server/src/database/commands/data-seed-dev-workspace.command.ts index 5207ca3bc..e58b13632 100644 --- a/server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/server/src/database/commands/data-seed-dev-workspace.command.ts @@ -2,19 +2,18 @@ import { Command, CommandRunner } from 'nest-commander'; import { DataSource } from 'typeorm'; import { DataSourceService } from 'src/metadata/data-source/data-source.service'; -import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service'; -import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service'; import { seedCompanies } from 'src/database/typeorm-seeds/workspace/companies'; -import { seedViewFields } from 'src/database/typeorm-seeds/workspace/view-fields'; import { seedViews } from 'src/database/typeorm-seeds/workspace/views'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { seedMetadataSchema } from 'src/database/typeorm-seeds/metadata'; import { seedOpportunity } from 'src/database/typeorm-seeds/workspace/opportunity'; import { seedPipelineStep } from 'src/database/typeorm-seeds/workspace/pipeline-step'; import { seedWorkspaceMember } from 'src/database/typeorm-seeds/workspace/workspaceMember'; import { seedPeople } from 'src/database/typeorm-seeds/workspace/people'; import { seedCoreSchema } from 'src/database/typeorm-seeds/core'; import { EnvironmentService } from 'src/integrations/environment/environment.service'; +import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync.metadata.service'; +import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; +import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service'; // TODO: implement dry-run @Command({ @@ -29,8 +28,9 @@ export class DataSeedWorkspaceCommand extends CommandRunner { private readonly environmentService: EnvironmentService, private readonly dataSourceService: DataSourceService, private readonly typeORMService: TypeORMService, - private readonly workspaceMigrationService: WorkspaceMigrationService, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, + private readonly workspaceDataSourceService: WorkspaceDataSourceService, + private readonly objectMetadataService: ObjectMetadataService, ) { super(); } @@ -41,13 +41,30 @@ export class DataSeedWorkspaceCommand extends CommandRunner { url: this.environmentService.getPGDatabaseUrl(), type: 'postgres', logging: true, - schema: 'public', + schema: 'core', }); await dataSource.initialize(); await seedCoreSchema(dataSource, this.workspaceId); - await seedMetadataSchema(dataSource); + + await dataSource.destroy(); + + const schemaName = await this.workspaceDataSourceService.createWorkspaceDBSchema( + this.workspaceId, + ); + + const dataSourceMetadata = + await this.dataSourceService.createDataSourceMetadata( + this.workspaceId, + schemaName, + ); + + await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata( + dataSourceMetadata.id, + this.workspaceId, + ); + } catch (error) { console.error(error); @@ -68,20 +85,27 @@ export class DataSeedWorkspaceCommand extends CommandRunner { } try { - await this.workspaceMigrationService.insertStandardMigrations( - this.workspaceId, - ); - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - this.workspaceId, - ); + const objectMetadata = await this.objectMetadataService.findManyWithinWorkspace(this.workspaceId); + const objectMetadataMap = objectMetadata.reduce((acc, object) => { + acc[object.nameSingular] = { + id: object.id, + fields: object.fields.reduce((acc, field) => { + acc[field.name] = field.id; + + return acc; + }, {}), + }; + + return acc; + }, {}); + await seedCompanies(workspaceDataSource, dataSourceMetadata.schema); await seedPeople(workspaceDataSource, dataSourceMetadata.schema); await seedPipelineStep(workspaceDataSource, dataSourceMetadata.schema); await seedOpportunity(workspaceDataSource, dataSourceMetadata.schema); - await seedViews(workspaceDataSource, dataSourceMetadata.schema); - await seedViewFields(workspaceDataSource, dataSourceMetadata.schema); + await seedViews(workspaceDataSource, dataSourceMetadata.schema, objectMetadataMap); await seedWorkspaceMember(workspaceDataSource, dataSourceMetadata.schema); } catch (error) { console.error(error); diff --git a/server/src/database/commands/database-command.module.ts b/server/src/database/commands/database-command.module.ts index ebef18aeb..f4c28d729 100644 --- a/server/src/database/commands/database-command.module.ts +++ b/server/src/database/commands/database-command.module.ts @@ -9,15 +9,19 @@ import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { WorkspaceModule } from 'src/core/workspace/workspace.module'; import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace.command'; +import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module'; +import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module'; +import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module'; @Module({ imports: [ WorkspaceManagerModule, DataSourceModule, TypeORMModule, - WorkspaceMigrationModule, - WorkspaceMigrationRunnerModule, WorkspaceModule, + WorkspaceDataSourceModule, + WorkspaceSyncMetadataModule, + ObjectMetadataModule, ], providers: [ DataSeedWorkspaceCommand, diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/view-filter.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/view-filter.ts index 463cf9b3f..125175589 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/view-filter.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/view-filter.ts @@ -124,19 +124,19 @@ export const seedViewFilterFieldMetadata = async ( isCustom: false, workspaceId: SeedWorkspaceId, isActive: true, - type: FieldMetadataType.UUID, - name: 'viewId', - label: 'View Id', + type: FieldMetadataType.RELATION, + name: 'view', + label: 'View', targetColumnMap: {}, description: 'View Filter related view', icon: 'IconLayoutCollage', - isNullable: false, + isNullable: true, isSystem: false, defaultValue: undefined, }, { id: SeedViewFilterFieldMetadataIds.ViewForeignKey, - objectMetadataId: SeedObjectMetadataIds.ViewField, + objectMetadataId: SeedObjectMetadataIds.ViewFilter, isCustom: false, workspaceId: SeedWorkspaceId, isActive: true, diff --git a/server/src/database/typeorm-seeds/workspace/view-fields.ts b/server/src/database/typeorm-seeds/workspace/view-fields.ts deleted file mode 100644 index 752bb6446..000000000 --- a/server/src/database/typeorm-seeds/workspace/view-fields.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { DataSource } from 'typeorm'; - -import { SeedViewIds } from 'src/database/typeorm-seeds/workspace/views'; -import { SeedCompanyFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/company'; -import { SeedPersonFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/person'; -import { SeedOpportunityFieldMetadataIds } from 'src/database/typeorm-seeds/metadata/field-metadata/opportunity'; - -const tableName = 'viewField'; - -export const seedViewFields = async ( - workspaceDataSource: DataSource, - schemaName: string, -) => { - await workspaceDataSource - .createQueryBuilder() - .insert() - .into(`${schemaName}.${tableName}`, [ - 'fieldMetadataId', - 'viewId', - 'position', - 'isVisible', - 'size', - ]) - .orIgnore() - .values([ - { - fieldMetadataId: SeedCompanyFieldMetadataIds.Name, - viewId: SeedViewIds.Company, - position: 0, - isVisible: true, - size: 180, - }, - { - fieldMetadataId: SeedCompanyFieldMetadataIds.DomainName, - viewId: SeedViewIds.Company, - position: 1, - isVisible: true, - size: 100, - }, - { - fieldMetadataId: SeedCompanyFieldMetadataIds.AccountOwner, - viewId: SeedViewIds.Company, - position: 2, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedCompanyFieldMetadataIds.CreatedAt, - viewId: SeedViewIds.Company, - position: 3, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedCompanyFieldMetadataIds.Employees, - viewId: SeedViewIds.Company, - position: 4, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedCompanyFieldMetadataIds.LinkedinLink, - viewId: SeedViewIds.Company, - position: 5, - isVisible: true, - size: 170, - }, - { - fieldMetadataId: SeedCompanyFieldMetadataIds.Address, - viewId: SeedViewIds.Company, - position: 6, - isVisible: true, - size: 170, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.Name, - viewId: SeedViewIds.Person, - position: 0, - isVisible: true, - size: 210, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.Email, - viewId: SeedViewIds.Person, - position: 1, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.Company, - viewId: SeedViewIds.Person, - position: 2, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.Phone, - viewId: SeedViewIds.Person, - position: 3, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.CreatedAt, - viewId: SeedViewIds.Person, - position: 4, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.City, - viewId: SeedViewIds.Person, - position: 5, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.JobTitle, - viewId: SeedViewIds.Person, - position: 6, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.LinkedinLink, - viewId: SeedViewIds.Person, - position: 7, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedPersonFieldMetadataIds.XLink, - viewId: SeedViewIds.Person, - position: 8, - isVisible: true, - size: 150, - }, - - { - fieldMetadataId: SeedOpportunityFieldMetadataIds.Amount, - viewId: SeedViewIds.Opportunity, - position: 0, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedOpportunityFieldMetadataIds.CloseDate, - viewId: SeedViewIds.Opportunity, - position: 1, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedOpportunityFieldMetadataIds.Probability, - viewId: SeedViewIds.Opportunity, - position: 2, - isVisible: true, - size: 150, - }, - { - fieldMetadataId: SeedOpportunityFieldMetadataIds.PointOfContact, - viewId: SeedViewIds.Opportunity, - position: 3, - isVisible: true, - size: 150, - }, - ]) - .execute(); -}; diff --git a/server/src/database/typeorm-seeds/workspace/views.ts b/server/src/database/typeorm-seeds/workspace/views.ts index 6fce2ea09..73a515688 100644 --- a/server/src/database/typeorm-seeds/workspace/views.ts +++ b/server/src/database/typeorm-seeds/workspace/views.ts @@ -1,48 +1,198 @@ import { DataSource } from 'typeorm'; -import { SeedObjectMetadataIds } from 'src/database/typeorm-seeds/metadata/object-metadata'; - -const tableName = 'view'; - -export const enum SeedViewIds { - Company = '20202020-2441-4424-8163-4002c523d415', - Person = '20202020-1979-447d-8115-593744eb4ead', - Opportunity = '20202020-b2b3-48a5-96ce-0936d6af21f7', -} +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; export const seedViews = async ( workspaceDataSource: DataSource, schemaName: string, + objectMetadataMap: Record, ) => { + const createdViews = await workspaceDataSource + .createQueryBuilder() + .insert() + .into(`${schemaName}.view`, [ + 'name', + 'objectMetadataId', + 'type', + ]) + .values([ + { + name: 'All Companies', + objectMetadataId: objectMetadataMap['company'].id, + type: 'table', + }, + { + name: 'All People', + objectMetadataId: objectMetadataMap['person'].id, + type: 'table', + }, + { + name: 'All Opportunities', + objectMetadataId: objectMetadataMap['opportunity'].id, + type: 'kanban', + }, + ]) + .returning('*') + .execute(); + + const viewIdMap = createdViews.raw.reduce((acc, view) => { + acc[view.name] = view.id; + + return acc; + }, {}); + await workspaceDataSource - .createQueryBuilder() - .insert() - .into(`${schemaName}.${tableName}`, [ - 'id', - 'name', - 'objectMetadataId', - 'type', - ]) - .orIgnore() - .values([ - { - id: SeedViewIds.Company, - name: 'All Companies', - objectMetadataId: SeedObjectMetadataIds.Company, - type: 'table', - }, - { - id: SeedViewIds.Person, - name: 'All People', - objectMetadataId: SeedObjectMetadataIds.Person, - type: 'table', - }, - { - id: SeedViewIds.Opportunity, - name: 'All Opportunities', - objectMetadataId: SeedObjectMetadataIds.Opportunity, - type: 'kanban', - }, - ]) - .execute(); + .createQueryBuilder() + .insert() + .into(`${schemaName}.viewField`, [ + 'fieldMetadataId', + 'viewId', + 'position', + 'isVisible', + 'size', + ]) + .values([ + { + fieldMetadataId: objectMetadataMap['company'].fields['name'], + viewId: viewIdMap['All Companies'], + position: 0, + isVisible: true, + size: 180, + }, + { + fieldMetadataId: objectMetadataMap['company'].fields['domainName'], + viewId: viewIdMap['All Companies'], + position: 1, + isVisible: true, + size: 100, + }, + { + fieldMetadataId: objectMetadataMap['company'].fields['accountOwner'], + viewId: viewIdMap['All Companies'], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['company'].fields['createdAt'], + viewId: viewIdMap['All Companies'], + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['company'].fields['employees'], + viewId: viewIdMap['All Companies'], + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['company'].fields['linkedinLink'], + viewId: viewIdMap['All Companies'], + position: 5, + isVisible: true, + size: 170, + }, + { + fieldMetadataId: objectMetadataMap['company'].fields['address'], + viewId: viewIdMap['All Companies'], + position: 6, + isVisible: true, + size: 170, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['name'], + viewId: viewIdMap['All People'], + position: 0, + isVisible: true, + size: 210, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['email'], + viewId: viewIdMap['All People'], + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['company'], + viewId: viewIdMap['All People'], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['phone'], + viewId: viewIdMap['All People'], + position: 3, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['createdAt'], + viewId: viewIdMap['All People'], + position: 4, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['city'], + viewId: viewIdMap['All People'], + position: 5, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['jobTitle'], + viewId: viewIdMap['All People'], + position: 6, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['linkedinLink'], + viewId: viewIdMap['All People'], + position: 7, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['person'].fields['xLink'], + viewId: viewIdMap['All People'], + position: 8, + isVisible: true, + size: 150, + }, + + { + fieldMetadataId: objectMetadataMap['opportunity'].fields['amount'], + viewId: viewIdMap['All Opportunities'], + position: 0, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['opportunity'].fields['closeDate'], + viewId: viewIdMap['All Opportunities'], + position: 1, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['opportunity'].fields['probability'], + viewId: viewIdMap['All Opportunities'], + position: 2, + isVisible: true, + size: 150, + }, + { + fieldMetadataId: objectMetadataMap['opportunity'].fields['pointOfContact'], + viewId: viewIdMap['All Opportunities'], + position: 3, + isVisible: true, + size: 150, + }, + ]) + .execute(); }; diff --git a/server/src/database/typeorm/typeorm.module.ts b/server/src/database/typeorm/typeorm.module.ts index 1b76eda41..6866a6d4a 100644 --- a/server/src/database/typeorm/typeorm.module.ts +++ b/server/src/database/typeorm/typeorm.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { typeORMCoreModuleOptions } from 'src/database/typeorm/core/core.datasource'; +import { EnvironmentModule } from 'src/integrations/environment/environment.module'; import { TypeORMService } from './typeorm.service'; @@ -27,6 +28,7 @@ const coreTypeORMFactory = async (): Promise => ({ useFactory: coreTypeORMFactory, name: 'core', }), + EnvironmentModule, ], providers: [TypeORMService], exports: [TypeORMService], diff --git a/server/src/metadata/field-metadata/composite-types/currency.composite-type.ts b/server/src/metadata/field-metadata/composite-types/currency.composite-type.ts index 4591a0cda..6d674e347 100644 --- a/server/src/metadata/field-metadata/composite-types/currency.composite-type.ts +++ b/server/src/metadata/field-metadata/composite-types/currency.composite-type.ts @@ -3,21 +3,21 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; -export const currencyObjectDefinition = { - id: FieldMetadataType.CURRENCY.toString(), - nameSingular: 'currency', - namePlural: 'currency', - labelSingular: 'Currency', - labelPlural: 'Currency', - targetTableName: '', - fields: [ +export const currencyFields = ( + fieldMetadata?: FieldMetadataInterface, +): FieldMetadataInterface[] => { + return [ { id: 'amountMicros', type: FieldMetadataType.NUMERIC, objectMetadataId: FieldMetadataType.CURRENCY.toString(), name: 'amountMicros', label: 'AmountMicros', - targetColumnMap: { value: 'amountMicros' }, + targetColumnMap: { + value: fieldMetadata + ? `${fieldMetadata.name}AmountMicros` + : 'amountMicros', + }, isNullable: true, } satisfies FieldMetadataInterface, { @@ -26,10 +26,24 @@ export const currencyObjectDefinition = { objectMetadataId: FieldMetadataType.CURRENCY.toString(), name: 'currencyCode', label: 'Currency Code', - targetColumnMap: { value: 'currencyCode' }, + targetColumnMap: { + value: fieldMetadata + ? `${fieldMetadata.name}CurrencyCode` + : 'currencyCode', + }, isNullable: true, } satisfies FieldMetadataInterface, - ], + ]; +}; + +export const currencyObjectDefinition = { + id: FieldMetadataType.CURRENCY.toString(), + nameSingular: 'currency', + namePlural: 'currency', + labelSingular: 'Currency', + labelPlural: 'Currency', + targetTableName: '', + fields: currencyFields(), fromRelations: [], toRelations: [], } satisfies ObjectMetadataInterface; diff --git a/server/src/metadata/field-metadata/composite-types/full-name.composite-type.ts b/server/src/metadata/field-metadata/composite-types/full-name.composite-type.ts index 0a9ac2ad4..fd4ce18da 100644 --- a/server/src/metadata/field-metadata/composite-types/full-name.composite-type.ts +++ b/server/src/metadata/field-metadata/composite-types/full-name.composite-type.ts @@ -3,21 +3,19 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; -export const fullNameObjectDefinition = { - id: FieldMetadataType.FULL_NAME.toString(), - nameSingular: 'fullName', - namePlural: 'fullName', - labelSingular: 'FullName', - labelPlural: 'FullName', - targetTableName: '', - fields: [ +export const fullNameFields = ( + fieldMetadata?: FieldMetadataInterface, +): FieldMetadataInterface[] => { + return [ { id: 'firstName', type: FieldMetadataType.TEXT, objectMetadataId: FieldMetadataType.FULL_NAME.toString(), name: 'firstName', label: 'First Name', - targetColumnMap: { value: 'firstName' }, + targetColumnMap: { + value: fieldMetadata ? `${fieldMetadata.name}FirstName` : 'firstName', + }, isNullable: true, } satisfies FieldMetadataInterface, { @@ -26,10 +24,22 @@ export const fullNameObjectDefinition = { objectMetadataId: FieldMetadataType.FULL_NAME.toString(), name: 'lastName', label: 'Last Name', - targetColumnMap: { value: 'lastName' }, + targetColumnMap: { + value: fieldMetadata ? `${fieldMetadata.name}LastName` : 'lastName', + }, isNullable: true, } satisfies FieldMetadataInterface, - ], + ]; +}; + +export const fullNameObjectDefinition = { + id: FieldMetadataType.FULL_NAME.toString(), + nameSingular: 'fullName', + namePlural: 'fullName', + labelSingular: 'FullName', + labelPlural: 'FullName', + targetTableName: '', + fields: fullNameFields(), fromRelations: [], toRelations: [], } satisfies ObjectMetadataInterface; diff --git a/server/src/metadata/field-metadata/composite-types/link.composite-type.ts b/server/src/metadata/field-metadata/composite-types/link.composite-type.ts index f3e2b64bd..caa2f98f2 100644 --- a/server/src/metadata/field-metadata/composite-types/link.composite-type.ts +++ b/server/src/metadata/field-metadata/composite-types/link.composite-type.ts @@ -3,21 +3,19 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; -export const linkObjectDefinition = { - id: FieldMetadataType.LINK.toString(), - nameSingular: 'link', - namePlural: 'link', - labelSingular: 'Link', - labelPlural: 'Link', - targetTableName: '', - fields: [ +export const linkFields = ( + fieldMetadata?: FieldMetadataInterface, +): FieldMetadataInterface[] => { + return [ { id: 'label', type: FieldMetadataType.TEXT, objectMetadataId: FieldMetadataType.LINK.toString(), name: 'label', label: 'Label', - targetColumnMap: { value: 'label' }, + targetColumnMap: { + value: fieldMetadata ? `${fieldMetadata.name}Label` : 'label', + }, isNullable: true, } satisfies FieldMetadataInterface, { @@ -26,10 +24,22 @@ export const linkObjectDefinition = { objectMetadataId: FieldMetadataType.LINK.toString(), name: 'url', label: 'Url', - targetColumnMap: { value: 'url' }, + targetColumnMap: { + value: fieldMetadata ? `${fieldMetadata.name}Url` : 'url', + }, isNullable: true, } satisfies FieldMetadataInterface, - ], + ]; +}; + +export const linkObjectDefinition = { + id: FieldMetadataType.LINK.toString(), + nameSingular: 'link', + namePlural: 'link', + labelSingular: 'Link', + labelPlural: 'Link', + targetTableName: '', + fields: linkFields(), fromRelations: [], toRelations: [], } satisfies ObjectMetadataInterface; diff --git a/server/src/metadata/field-metadata/utils/generate-default-value.ts b/server/src/metadata/field-metadata/utils/generate-default-value.ts new file mode 100644 index 000000000..83cb7a612 --- /dev/null +++ b/server/src/metadata/field-metadata/utils/generate-default-value.ts @@ -0,0 +1,23 @@ +import { FieldMetadataDefaultValue } from 'src/metadata/field-metadata/interfaces/field-metadata-default-value.interface'; + +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; + +export function generateDefaultValue( + type: FieldMetadataType, +): FieldMetadataDefaultValue { + switch (type) { + case FieldMetadataType.TEXT: + case FieldMetadataType.PHONE: + case FieldMetadataType.EMAIL: + return { + value: '', + }; + case FieldMetadataType.FULL_NAME: + return { + firstName: '', + lastName: '', + }; + default: + return null; + } +} diff --git a/server/src/metadata/workspace-migration/workspace-migration.factory.ts b/server/src/metadata/workspace-migration/workspace-migration.factory.ts index 9c575be06..164770d31 100644 --- a/server/src/metadata/workspace-migration/workspace-migration.factory.ts +++ b/server/src/metadata/workspace-migration/workspace-migration.factory.ts @@ -12,9 +12,13 @@ import { WorkspaceMigrationColumnActionType, } from 'src/metadata/workspace-migration/workspace-migration.entity'; import { isCompositeFieldMetadataType } from 'src/metadata/field-metadata/utils/is-composite-field-metadata-type.util'; -import { linkObjectDefinition } from 'src/metadata/field-metadata/composite-types/link.composite-type'; -import { currencyObjectDefinition } from 'src/metadata/field-metadata/composite-types/currency.composite-type'; -import { fullNameObjectDefinition } from 'src/metadata/field-metadata/composite-types/full-name.composite-type'; +import { fullNameFields } from 'src/metadata/field-metadata/composite-types/full-name.composite-type'; +import { currencyFields } from 'src/metadata/field-metadata/composite-types/currency.composite-type'; +import { linkFields } from 'src/metadata/field-metadata/composite-types/link.composite-type'; + +type CompositeFieldSplitterFunction = ( + fieldMetadata: FieldMetadataInterface, +) => FieldMetadataInterface[]; @Injectable() export class WorkspaceMigrationFactory { @@ -26,7 +30,10 @@ export class WorkspaceMigrationFactory { options?: WorkspaceColumnActionOptions; } >; - private compositeDefinitions = new Map(); + private compositeDefinitions = new Map< + string, + CompositeFieldSplitterFunction + >(); constructor( private readonly basicColumnActionFactory: BasicColumnActionFactory, @@ -83,11 +90,13 @@ export class WorkspaceMigrationFactory { ], ]); - this.compositeDefinitions = new Map([ - [FieldMetadataType.LINK, linkObjectDefinition.fields], - [FieldMetadataType.CURRENCY, currencyObjectDefinition.fields], - [FieldMetadataType.FULL_NAME, fullNameObjectDefinition.fields], - ]); + this.compositeDefinitions = new Map( + [ + [FieldMetadataType.LINK, linkFields], + [FieldMetadataType.CURRENCY, currencyFields], + [FieldMetadataType.FULL_NAME, fullNameFields], + ], + ); } createColumnActions( @@ -128,11 +137,11 @@ export class WorkspaceMigrationFactory { // If it's a composite field type, we need to create a column action for each of the fields if (isCompositeFieldMetadataType(alteredFieldMetadata.type)) { - const fieldMetadataCollection = this.compositeDefinitions.get( + const fieldMetadataSplitterFunction = this.compositeDefinitions.get( alteredFieldMetadata.type, ); - if (!fieldMetadataCollection) { + if (!fieldMetadataSplitterFunction) { this.logger.error( `No composite definition found for type ${alteredFieldMetadata.type}`, { @@ -145,6 +154,9 @@ export class WorkspaceMigrationFactory { ); } + const fieldMetadataCollection = + fieldMetadataSplitterFunction(alteredFieldMetadata); + return fieldMetadataCollection.map((fieldMetadata) => this.createColumnAction(action, fieldMetadata, fieldMetadata), ); diff --git a/server/src/workspace/workspace-manager/decorators/metadata.decorator.ts b/server/src/workspace/workspace-manager/decorators/metadata.decorator.ts deleted file mode 100644 index b29ce3d9e..000000000 --- a/server/src/workspace/workspace-manager/decorators/metadata.decorator.ts +++ /dev/null @@ -1,103 +0,0 @@ -import camelCase from 'lodash.camelcase'; -import 'reflect-metadata'; - -import { FieldMetadataDefaultValue } from 'src/metadata/field-metadata/interfaces/field-metadata-default-value.interface'; - -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; -import { generateTargetColumnMap } from 'src/metadata/field-metadata/utils/generate-target-column-map.util'; - -export type FieldMetadataDecorator = { - type: FieldMetadataType; - label: string; - description?: string | null; - icon?: string | null; - defaultValue?: FieldMetadataDefaultValue | null; -}; - -export type ObjectMetadataDecorator = { - namePlural: string; - labelSingular: string; - labelPlural: string; - description?: string | null; - icon?: string | null; -}; - -const classSuffix = 'ObjectMetadata'; - -export function ObjectMetadata( - metadata: ObjectMetadataDecorator, -): ClassDecorator { - return (target) => { - const isSystem = Reflect.getMetadata('isSystem', target) || false; - - let objectName = camelCase(target.name); - - if (objectName.endsWith(classSuffix)) { - objectName = objectName.slice(0, -classSuffix.length); - } - - Reflect.defineMetadata( - 'objectMetadata', - { - nameSingular: objectName, - ...metadata, - targetTableName: objectName, - isSystem, - isCustom: false, - isActive: true, - }, - target, - ); - }; -} - -export function IsNullable() { - return function (target: object, fieldKey: string) { - Reflect.defineMetadata('isNullable', true, target, fieldKey); - }; -} - -export function IsSystem() { - return function (target: object, fieldKey?: string) { - if (fieldKey) { - Reflect.defineMetadata('isSystem', true, target, fieldKey); - } else { - Reflect.defineMetadata('isSystem', true, target); - } - }; -} - -export function FieldMetadata( - metadata: FieldMetadataDecorator, -): PropertyDecorator { - return (target: object, fieldKey: string) => { - const existingFieldMetadata = - Reflect.getMetadata('fieldMetadata', target.constructor) || {}; - - const isNullable = - Reflect.getMetadata('isNullable', target, fieldKey) || false; - - const isSystem = Reflect.getMetadata('isSystem', target, fieldKey) || false; - - Reflect.defineMetadata( - 'fieldMetadata', - { - ...existingFieldMetadata, - [fieldKey]: { - name: fieldKey, - ...metadata, - targetColumnMap: generateTargetColumnMap( - metadata.type, - false, - fieldKey, - ), - isNullable, - isSystem, - isCustom: false, - isActive: true, - }, - }, - target.constructor, - ); - }; -} diff --git a/server/src/workspace/workspace-manager/standard-objects/activity-target.ts b/server/src/workspace/workspace-manager/standard-objects/activity-target.ts deleted file mode 100644 index d4075b4db..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/activity-target.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const activityTargetMetadata = { - nameSingular: 'activityTarget', - namePlural: 'activityTargets', - labelSingular: 'Activity Target', - labelPlural: 'Activity Targets', - targetTableName: 'activityTarget', - description: 'An activity target', - icon: 'IconCheckbox', - isActive: true, - isSystem: true, - fields: [ - { - // Relations - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activity', - label: 'Activity', - targetColumnMap: {}, - description: 'ActivityTarget activity', - icon: 'IconCheckbox', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'person', - label: 'Person', - targetColumnMap: {}, - description: 'ActivityTarget person', - icon: 'IconUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - targetColumnMap: {}, - description: 'ActivityTarget company', - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'activityId', - label: 'Activity id (foreign key)', - targetColumnMap: {}, - description: 'ActivityTarget activity id foreign key', - icon: undefined, - isNullable: false, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'personId', - label: 'Person id (foreign key)', - targetColumnMap: {}, - description: 'ActivityTarget person id foreign key', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'companyId', - label: 'Company id (foreign key)', - targetColumnMap: {}, - description: 'ActivityTarget company id foreign key', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - ], -}; - -export default activityTargetMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/activity.ts b/server/src/workspace/workspace-manager/standard-objects/activity.ts deleted file mode 100644 index b0a8c1533..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/activity.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const activityMetadata = { - nameSingular: 'activity', - namePlural: 'activities', - labelSingular: 'Activity', - labelPlural: 'Activities', - targetTableName: 'activity', - description: 'An activity', - icon: 'IconCheckbox', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'title', - label: 'Title', - targetColumnMap: { - value: 'title', - }, - description: 'Activity title', - icon: 'IconNotes', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'body', - label: 'Body', - targetColumnMap: { - value: 'body', - }, - description: 'Activity body', - icon: 'IconList', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'type', - label: 'Type', - targetColumnMap: { - value: 'type', - }, - description: 'Activity type', - icon: 'IconCheckbox', - isNullable: false, - defaultValue: { value: 'Note' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.DATE_TIME, - name: 'reminderAt', - label: 'Reminder Date', - targetColumnMap: { - value: 'reminderAt', - }, - description: 'Activity reminder date', - icon: 'IconCalendarEvent', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.DATE_TIME, - name: 'dueAt', - label: 'Due Date', - targetColumnMap: { - value: 'dueAt', - }, - description: 'Activity due date', - icon: 'IconCalendarEvent', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.DATE_TIME, - name: 'completedAt', - label: 'Completion Date', - targetColumnMap: { - value: 'completedAt', - }, - description: 'Activity completion date', - icon: 'IconCheck', - isNullable: true, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activityTargets', - label: 'Targets', - targetColumnMap: {}, - description: 'Activity targets', - icon: 'IconCheckbox', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'attachments', - label: 'Attachments', - targetColumnMap: {}, - description: 'Activity attachments', - icon: 'IconFileImport', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'comments', - label: 'Comments', - targetColumnMap: {}, - description: 'Activity comments', - icon: 'IconComment', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'author', - label: 'Author', - targetColumnMap: {}, - description: - 'Activity author. This is the person who created the activity', - icon: 'IconUserCircle', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'authorId', - label: 'Author id (foreign key)', - targetColumnMap: {}, - description: 'Activity author id foreign key', - icon: undefined, - isNullable: false, - isSystem: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'assignee', - label: 'Assignee', - targetColumnMap: {}, - description: - 'Acitivity assignee. This is the workspace member assigned to the activity ', - icon: 'IconUserCircle', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'assigneeId', - label: 'Assignee id (foreign key)', - targetColumnMap: {}, - description: 'Acitivity assignee id foreign key', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - ], -}; - -export default activityMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/api-key.ts b/server/src/workspace/workspace-manager/standard-objects/api-key.ts deleted file mode 100644 index 417448829..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/api-key.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const apiKeyMetadata = { - nameSingular: 'apiKey', - namePlural: 'apiKeys', - labelSingular: 'Api Key', - labelPlural: 'Api Keys', - targetTableName: 'apiKey', - description: 'An api key', - icon: 'IconRobot', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - targetColumnMap: { - value: 'name', - }, - description: 'ApiKey name', - icon: 'IconLink', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.DATE_TIME, - name: 'expiresAt', - label: 'Expiration date', - targetColumnMap: { - value: 'expiresAt', - }, - description: 'ApiKey expiration date', - icon: 'IconCalendar', - isNullable: false, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.DATE_TIME, - name: 'revokedAt', - label: 'Revocation date', - targetColumnMap: { - value: 'revokedAt', - }, - description: 'ApiKey revocation date', - icon: 'IconCalendar', - isNullable: true, - }, - ], -}; - -export default apiKeyMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/attachment.ts b/server/src/workspace/workspace-manager/standard-objects/attachment.ts deleted file mode 100644 index d81a3f2d2..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/attachment.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const attachmentMetadata = { - nameSingular: 'attachment', - namePlural: 'attachments', - labelSingular: 'Attachment', - labelPlural: 'Attachments', - targetTableName: 'attachment', - description: 'An attachment', - icon: 'IconFileImport', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - targetColumnMap: { - value: 'name', - }, - description: 'Attachment name', - icon: 'IconFileUpload', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'fullPath', - label: 'Full path', - targetColumnMap: { - value: 'fullPath', - }, - description: 'Attachment full path', - icon: 'IconLink', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'type', - label: 'Type', - targetColumnMap: { - value: 'type', - }, - description: 'Attachment type', - icon: 'IconList', - isNullable: false, - defaultValue: { value: '' }, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'author', - label: 'Author', - targetColumnMap: { - value: 'authorId', - }, - description: 'Attachment author', - icon: 'IconCircleUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'authorId', - label: 'Author id (foreign key)', - targetColumnMap: {}, - description: 'Activity author id foreign key', - icon: undefined, - isNullable: false, - isSystem: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activity', - label: 'Activity', - targetColumnMap: { - value: 'activityId', - }, - description: 'Attachment activity', - icon: 'IconNotes', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'person', - label: 'Person', - targetColumnMap: { - value: 'personId', - }, - description: 'Attachment person', - icon: 'IconUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - targetColumnMap: { - value: 'companyId', - }, - description: 'Attachment company', - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - ], -}; - -export default attachmentMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/comment.ts b/server/src/workspace/workspace-manager/standard-objects/comment.ts deleted file mode 100644 index cc56c59c8..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/comment.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const commentMetadata = { - nameSingular: 'comment', - namePlural: 'comments', - labelSingular: 'Comment', - labelPlural: 'Comments', - targetTableName: 'comment', - description: 'A comment', - icon: 'IconMessageCircle', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'body', - label: 'Body', - targetColumnMap: { - value: 'body', - }, - description: 'Comment body', - icon: 'IconLink', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'authorId', - label: 'Author', - targetColumnMap: {}, - description: 'Comment author', - icon: 'IconCircleUser', - isNullable: true, - isSystem: true, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'author', - label: 'Author', - targetColumnMap: {}, - description: 'Comment author', - icon: 'IconCircleUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activity', - label: 'Activity', - targetColumnMap: {}, - description: 'Comment activity', - icon: 'IconNotes', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'activityId', - label: 'Activity', - targetColumnMap: {}, - description: 'Comment activity', - icon: 'IconNotes', - isNullable: true, - isSystem: true, - }, - ], -}; - -export default commentMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/company.ts b/server/src/workspace/workspace-manager/standard-objects/company.ts deleted file mode 100644 index 704b5bc15..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/company.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const companyMetadata = { - nameSingular: 'company', - namePlural: 'companies', - labelSingular: 'Company', - labelPlural: 'Companies', - targetTableName: 'company', - description: 'A company', - icon: 'IconBuildingSkyscraper', - isActive: true, - isSystem: false, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - targetColumnMap: { - value: 'name', - }, - description: 'The company name', - icon: 'IconBuildingSkyscraper', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'domainName', - label: 'Domain Name', - targetColumnMap: { - value: 'domainName', - }, - description: - 'The company website URL. We use this url to fetch the company icon', - icon: 'IconLink', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'address', - label: 'Address', - targetColumnMap: { - value: 'address', - }, - description: 'The company address', - icon: 'IconMap', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.NUMBER, - name: 'employees', - label: 'Employees', - targetColumnMap: { - value: 'employees', - }, - description: 'Number of employees in the company', - icon: 'IconUsers', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.LINK, - name: 'linkedinLink', - label: 'Linkedin', - targetColumnMap: { - label: 'linkedinLinkLabel', - url: 'linkedinLinkUrl', - }, - description: 'The company Linkedin account', - icon: 'IconBrandLinkedin', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.LINK, - name: 'xLink', - label: 'X', - targetColumnMap: { - label: 'xLinkLabel', - url: 'xLinkUrl', - }, - description: 'The company Twitter/X account', - icon: 'IconBrandX', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.CURRENCY, - name: 'annualRecurringRevenue', - label: 'ARR', - targetColumnMap: { - amountMicros: 'annualRecurringRevenueAmountMicros', - currencyCode: 'annualRecurringRevenueCurrencyCode', - }, - description: - 'Annual Recurring Revenue: The actual or estimated annual revenue of the company', - icon: 'IconMoneybag', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.BOOLEAN, - name: 'idealCustomerProfile', - label: 'ICP', - targetColumnMap: { - value: 'idealCustomerProfile', - }, - description: - 'Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you', - icon: 'IconTarget', - isNullable: true, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'people', - label: 'People', - targetColumnMap: {}, - description: 'People linked to the company.', - icon: 'IconUsers', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'accountOwner', - label: 'Account Owner', - targetColumnMap: { - value: 'accountOwnerId', - }, - description: - 'Your team member responsible for managing the company account', - icon: 'IconUserCircle', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'accountOwnerId', - label: 'Account Owner ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for account owner', - icon: undefined, - isNullable: true, - isSystem: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activityTargets', - label: 'Activities', - targetColumnMap: {}, - description: 'Activities tied to the company', - icon: 'IconCheckbox', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'opportunities', - label: 'Opportunities', - targetColumnMap: {}, - description: 'Opportunities linked to the company.', - icon: 'IconTargetArrow', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'favorites', - label: 'Favorites', - targetColumnMap: {}, - description: 'Favorites linked to the company', - icon: 'IconHeart', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'attachments', - label: 'Attachments', - targetColumnMap: {}, - description: 'Attachments linked to the company.', - icon: 'IconFileImport', - isNullable: true, - }, - ], -}; - -export default companyMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/favorite.ts b/server/src/workspace/workspace-manager/standard-objects/favorite.ts deleted file mode 100644 index 8e5858fcf..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/favorite.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const favoriteMetadata = { - nameSingular: 'favorite', - namePlural: 'favorites', - labelSingular: 'Favorite', - labelPlural: 'Favorites', - targetTableName: 'favorite', - description: 'A favorite', - icon: 'IconHeart', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.NUMBER, - name: 'position', - label: 'Position', - targetColumnMap: { - value: 'position', - }, - description: 'Favorite position', - icon: 'IconList', - isNullable: false, - defaultValue: { value: 0 }, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'workspaceMember', - label: 'Workspace Member', - targetColumnMap: {}, - description: 'Favorite workspace member', - icon: 'IconCircleUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'person', - label: 'Person', - targetColumnMap: {}, - description: 'Favorite person', - icon: 'IconUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - targetColumnMap: {}, - description: 'Favorite company', - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'workspaceMemberId', - label: 'Workspace Member ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for workspace member', - icon: undefined, - isNullable: false, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'personId', - label: 'Person ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for person', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'companyId', - label: 'Company ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for company', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - ], -}; - -export default favoriteMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/opportunity.ts b/server/src/workspace/workspace-manager/standard-objects/opportunity.ts deleted file mode 100644 index 2739e1804..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/opportunity.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const opportunityMetadata = { - nameSingular: 'opportunity', - namePlural: 'opportunities', - labelSingular: 'Opportunity', - labelPlural: 'Opportunities', - targetTableName: 'opportunity', - description: 'An opportunity', - icon: 'IconTargetArrow', - isActive: true, - isSystem: false, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.CURRENCY, - name: 'amount', - label: 'Amount', - targetColumnMap: { - amountMicros: 'amountAmountMicros', - currencyCode: 'amountCurrencyCode', - }, - description: 'Opportunity amount', - icon: 'IconCurrencyDollar', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.DATE_TIME, - name: 'closeDate', - label: 'Close date', - targetColumnMap: { - value: 'closeDate', - }, - description: 'Opportunity close date', - icon: 'IconCalendarEvent', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'probability', - label: 'Probability', - targetColumnMap: { - value: 'probability', - }, - description: 'Opportunity probability', - icon: 'IconProgressCheck', - isNullable: true, - defaultValue: { value: '0' }, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'pipelineStep', - label: 'Pipeline Step', - targetColumnMap: { - value: 'pipelineStepId', - }, - description: 'Opportunity pipeline step', - icon: 'IconKanban', - isSystem: true, - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'pointOfContact', - label: 'Point of Contact', - targetColumnMap: { - value: 'pointOfContactId', - }, - description: 'Opportunity point of contact', - icon: 'IconUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'person', - label: 'Person', - targetColumnMap: { - value: 'personId', - }, - description: 'Opportunity person', - icon: 'IconUser', - isNullable: true, - isSystem: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - targetColumnMap: { - value: 'companyId', - }, - description: 'Opportunity company', - icon: 'IconBuildingSkyscraper', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'companyId', - label: 'Company ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for company', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'personId', - label: 'Person ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for person', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'pointOfContactId', - label: 'Point of Contact ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for point of contact', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'pipelineStepId', - label: 'Pipeline Step ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for pipeline step', - icon: undefined, - isNullable: true, - isSystem: true, - defaultValue: undefined, - }, - ], -}; - -export default opportunityMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/person.ts b/server/src/workspace/workspace-manager/standard-objects/person.ts deleted file mode 100644 index 1b35445e4..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/person.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const personMetadata = { - nameSingular: 'person', - namePlural: 'people', - labelSingular: 'Person', - labelPlural: 'People', - targetTableName: 'person', - description: 'A person', - icon: 'IconUser', - isActive: true, - isSystem: false, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.FULL_NAME, - name: 'name', - label: 'Name', - targetColumnMap: { - firstName: 'nameFirstName', - lastName: 'nameLastName', - }, - description: 'Contact’s name', - icon: 'IconUser', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.EMAIL, - name: 'email', - label: 'Email', - targetColumnMap: { - value: 'email', - }, - description: 'Contact’s Email', - icon: 'IconMail', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.LINK, - name: 'linkedinLink', - label: 'Linkedin', - targetColumnMap: { - label: 'linkedinLinkLabel', - url: 'linkedinLinkUrl', - }, - description: 'Contact’s Linkedin account', - icon: 'IconBrandLinkedin', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.LINK, - name: 'xLink', - label: 'X', - targetColumnMap: { - label: 'xLinkLabel', - url: 'xLinkUrl', - }, - description: 'Contact’s X/Twitter account', - icon: 'IconBrandX', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'jobTitle', - label: 'Job Title', - targetColumnMap: { - value: 'jobTitle', - }, - description: 'Contact’s job title', - icon: 'IconBriefcase', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'phone', - label: 'Phone', - targetColumnMap: { - value: 'phone', - }, - description: 'Contact’s phone number', - icon: 'IconPhone', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'city', - label: 'City', - targetColumnMap: { - value: 'city', - }, - description: 'Contact’s city', - icon: 'IconMap', - isNullable: true, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'avatarUrl', - label: 'Avatar', - targetColumnMap: { - value: 'avatarUrl', - }, - description: 'Contact’s avatar', - icon: 'IconFileUpload', - isNullable: true, - isSystem: true, - defaultValue: { value: '' }, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - targetColumnMap: {}, - description: 'Contact’s company', - icon: 'IconBuildingSkyscraper', - isNullable: true, - isSystem: false, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'companyId', - label: 'Company ID (foreign key)', - targetColumnMap: {}, - description: 'Foreign key for company', - icon: undefined, - isNullable: true, - isSystem: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'pointOfContactForOpportunities', - label: 'POC for Opportunities', - targetColumnMap: {}, - description: 'Point of Contact for Opportunities', - icon: 'IconTargetArrow', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'activityTargets', - label: 'Activities', - targetColumnMap: {}, - description: 'Activities tied to the contact', - icon: 'IconCheckbox', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'opportunities', - label: 'Opportunities', - targetColumnMap: {}, - description: 'Opportunities linked to the contact.', - icon: 'IconTargetArrow', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'favorites', - label: 'Favorites', - targetColumnMap: {}, - description: 'Favorites linked to the contact', - icon: 'IconHeart', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'attachments', - label: 'Attachments', - targetColumnMap: {}, - description: 'Attachments linked to the contact.', - icon: 'IconFileImport', - isNullable: true, - }, - ], -}; - -export default personMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/pipeline-step.ts b/server/src/workspace/workspace-manager/standard-objects/pipeline-step.ts deleted file mode 100644 index 428dabf1e..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/pipeline-step.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const pipelineStepMetadata = { - nameSingular: 'pipelineStep', - namePlural: 'pipelineSteps', - labelSingular: 'Pipeline Step', - labelPlural: 'Pipeline Steps', - targetTableName: 'pipelineStep', - description: 'A pipeline step', - icon: 'IconLayoutKanban', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - targetColumnMap: { - value: 'name', - }, - description: 'Pipeline Step name', - icon: 'IconCurrencyDollar', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'color', - label: 'Color', - targetColumnMap: { - value: 'color', - }, - description: 'Pipeline Step color', - icon: 'IconColorSwatch', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.NUMBER, - name: 'position', - label: 'Position', - targetColumnMap: { - value: 'position', - }, - description: 'Pipeline Step position', - icon: 'IconHierarchy2', - isNullable: false, - defaultValue: { value: 0 }, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'opportunities', - label: 'Opportunities', - targetColumnMap: {}, - description: 'Opportunities linked to the step.', - icon: 'IconTargetArrow', - isNullable: true, - }, - ], -}; - -export default pipelineStepMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/relations/activity.ts b/server/src/workspace/workspace-manager/standard-objects/relations/activity.ts deleted file mode 100644 index b728b7f57..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/relations/activity.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; - -const activityRelationMetadata = [ - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'activity', - toObjectNameSingular: 'activityTarget', - fromFieldMetadataName: 'activityTargets', - toFieldMetadataName: 'activity', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'activity', - toObjectNameSingular: 'attachment', - fromFieldMetadataName: 'attachments', - toFieldMetadataName: 'activity', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'activity', - toObjectNameSingular: 'comment', - fromFieldMetadataName: 'comments', - toFieldMetadataName: 'activity', - }, -]; - -export default activityRelationMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/relations/company.ts b/server/src/workspace/workspace-manager/standard-objects/relations/company.ts deleted file mode 100644 index 880a4ab0b..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/relations/company.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; - -const companyRelationMetadata = [ - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'company', - toObjectNameSingular: 'person', - fromFieldMetadataName: 'people', - toFieldMetadataName: 'company', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'company', - toObjectNameSingular: 'favorite', - fromFieldMetadataName: 'favorites', - toFieldMetadataName: 'company', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'company', - toObjectNameSingular: 'attachment', - fromFieldMetadataName: 'attachments', - toFieldMetadataName: 'company', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'company', - toObjectNameSingular: 'opportunity', - fromFieldMetadataName: 'opportunities', - toFieldMetadataName: 'company', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'company', - toObjectNameSingular: 'activityTarget', - fromFieldMetadataName: 'activityTargets', - toFieldMetadataName: 'company', - }, -]; - -export default companyRelationMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/relations/person.ts b/server/src/workspace/workspace-manager/standard-objects/relations/person.ts deleted file mode 100644 index facfa8e43..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/relations/person.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; - -const personRelationMetadata = [ - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'person', - toObjectNameSingular: 'favorite', - fromFieldMetadataName: 'favorites', - toFieldMetadataName: 'person', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'person', - toObjectNameSingular: 'attachment', - fromFieldMetadataName: 'attachments', - toFieldMetadataName: 'person', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'person', - toObjectNameSingular: 'opportunity', - fromFieldMetadataName: 'opportunities', - toFieldMetadataName: 'person', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'person', - toObjectNameSingular: 'opportunity', - fromFieldMetadataName: 'pointOfContactForOpportunities', - toFieldMetadataName: 'pointOfContact', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'person', - toObjectNameSingular: 'activityTarget', - fromFieldMetadataName: 'activityTargets', - toFieldMetadataName: 'person', - }, -]; - -export default personRelationMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/relations/pipeline-step.ts b/server/src/workspace/workspace-manager/standard-objects/relations/pipeline-step.ts deleted file mode 100644 index a590b598f..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/relations/pipeline-step.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; - -const pipelineStepRelationMetadata = [ - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'pipelineStep', - toObjectNameSingular: 'opportunity', - fromFieldMetadataName: 'opportunities', - toFieldMetadataName: 'pipelineStep', - }, -]; - -export default pipelineStepRelationMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/relations/view.ts b/server/src/workspace/workspace-manager/standard-objects/relations/view.ts deleted file mode 100644 index 658d90d52..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/relations/view.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; - -const viewRelationMetadata = [ - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'view', - toObjectNameSingular: 'viewField', - fromFieldMetadataName: 'viewFields', - toFieldMetadataName: 'view', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'view', - toObjectNameSingular: 'viewFilter', - fromFieldMetadataName: 'viewFilters', - toFieldMetadataName: 'view', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'view', - toObjectNameSingular: 'viewSort', - fromFieldMetadataName: 'viewSorts', - toFieldMetadataName: 'view', - }, -]; - -export default viewRelationMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/relations/workspace-member.ts b/server/src/workspace/workspace-manager/standard-objects/relations/workspace-member.ts deleted file mode 100644 index 5959f383a..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/relations/workspace-member.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; - -const workspaceMemberRelationMetadata = [ - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'workspaceMember', - toObjectNameSingular: 'company', - fromFieldMetadataName: 'accountOwnerForCompanies', - toFieldMetadataName: 'accountOwner', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'workspaceMember', - toObjectNameSingular: 'favorite', - fromFieldMetadataName: 'favorites', - toFieldMetadataName: 'workspaceMember', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'workspaceMember', - toObjectNameSingular: 'activity', - fromFieldMetadataName: 'authoredActivities', - toFieldMetadataName: 'author', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'workspaceMember', - toObjectNameSingular: 'activity', - fromFieldMetadataName: 'assignedActivities', - toFieldMetadataName: 'assignee', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'workspaceMember', - toObjectNameSingular: 'comment', - fromFieldMetadataName: 'authoredComments', - toFieldMetadataName: 'author', - }, - { - type: RelationMetadataType.ONE_TO_MANY, - fromObjectNameSingular: 'workspaceMember', - toObjectNameSingular: 'attachment', - fromFieldMetadataName: 'authoredAttachments', - toFieldMetadataName: 'author', - }, -]; - -export default workspaceMemberRelationMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts b/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts deleted file mode 100644 index 90b2d6b72..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts +++ /dev/null @@ -1,82 +0,0 @@ -import apiKeyMetadata from 'src/workspace/workspace-manager/standard-objects/api-key'; -import { - FieldMetadataEntity, - FieldMetadataType, -} from 'src/metadata/field-metadata/field-metadata.entity'; -import activityMetadata from 'src/workspace/workspace-manager/standard-objects/activity'; -import activityTargetMetadata from 'src/workspace/workspace-manager/standard-objects/activity-target'; -import attachmentMetadata from 'src/workspace/workspace-manager/standard-objects/attachment'; -import commentMetadata from 'src/workspace/workspace-manager/standard-objects/comment'; -import companyMetadata from 'src/workspace/workspace-manager/standard-objects/company'; -import favoriteMetadata from 'src/workspace/workspace-manager/standard-objects/favorite'; -import opportunityMetadata from 'src/workspace/workspace-manager/standard-objects/opportunity'; -import personMetadata from 'src/workspace/workspace-manager/standard-objects/person'; -import pipelineStepMetadata from 'src/workspace/workspace-manager/standard-objects/pipeline-step'; -import viewMetadata from 'src/workspace/workspace-manager/standard-objects/view'; -import viewFieldMetadata from 'src/workspace/workspace-manager/standard-objects/view-field'; -import viewFilterMetadata from 'src/workspace/workspace-manager/standard-objects/view-filter'; -import viewSortMetadata from 'src/workspace/workspace-manager/standard-objects/view-sort'; -import workspaceMemberMetadata from 'src/workspace/workspace-manager/standard-objects/workspace-member'; -import connectedAccountMetadata from 'src/workspace/workspace-manager/standard-objects/connected-account'; - -export const standardObjectsMetadata = { - activityTarget: activityTargetMetadata, - activity: activityMetadata, - apiKey: apiKeyMetadata, - attachment: attachmentMetadata, - comment: commentMetadata, - company: companyMetadata, - connectedAccount: connectedAccountMetadata, - favorite: favoriteMetadata, - opportunity: opportunityMetadata, - person: personMetadata, - pipelineStep: pipelineStepMetadata, - viewField: viewFieldMetadata, - viewFilter: viewFilterMetadata, - viewSort: viewSortMetadata, - view: viewMetadata, - workspaceMember: workspaceMemberMetadata, -}; - -export const basicFieldsMetadata: Partial[] = [ - { - name: 'id', - label: 'Id', - type: FieldMetadataType.UUID, - targetColumnMap: { - value: 'id', - }, - isNullable: true, - isSystem: true, - isCustom: false, - isActive: true, - defaultValue: { type: 'uuid' }, - }, - { - name: 'createdAt', - label: 'Creation date', - type: FieldMetadataType.DATE_TIME, - targetColumnMap: { - value: 'createdAt', - }, - icon: 'IconCalendar', - isNullable: true, - isCustom: false, - isActive: true, - defaultValue: { type: 'now' }, - }, - { - name: 'updatedAt', - label: 'Update date', - type: FieldMetadataType.DATE_TIME, - targetColumnMap: { - value: 'updatedAt', - }, - icon: 'IconCalendar', - isNullable: true, - isCustom: false, - isSystem: true, - isActive: true, - defaultValue: { type: 'now' }, - }, -]; diff --git a/server/src/workspace/workspace-manager/standard-objects/standard-object-relation-metadata.ts b/server/src/workspace/workspace-manager/standard-objects/standard-object-relation-metadata.ts deleted file mode 100644 index f77de6987..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/standard-object-relation-metadata.ts +++ /dev/null @@ -1,15 +0,0 @@ -import activityRelationMetadata from 'src/workspace/workspace-manager/standard-objects/relations/activity'; -import companyRelationMetadata from 'src/workspace/workspace-manager/standard-objects/relations/company'; -import personRelationMetadata from 'src/workspace/workspace-manager/standard-objects/relations/person'; -import pipelineStepRelationMetadata from 'src/workspace/workspace-manager/standard-objects/relations/pipeline-step'; -import viewRelationMetadata from 'src/workspace/workspace-manager/standard-objects/relations/view'; -import workspaceMemberRelationMetadata from 'src/workspace/workspace-manager/standard-objects/relations/workspace-member'; - -export const standardObjectRelationMetadata = [ - ...activityRelationMetadata, - ...companyRelationMetadata, - ...personRelationMetadata, - ...pipelineStepRelationMetadata, - ...viewRelationMetadata, - ...workspaceMemberRelationMetadata, -]; diff --git a/server/src/workspace/workspace-manager/standard-objects/view-field.ts b/server/src/workspace/workspace-manager/standard-objects/view-field.ts deleted file mode 100644 index b8fd48157..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/view-field.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const viewFieldMetadata = { - nameSingular: 'viewField', - namePlural: 'viewFields', - labelSingular: 'View Field', - labelPlural: 'View Fields', - targetTableName: 'viewField', - description: '(System) View Fields', - icon: 'IconTag', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'fieldMetadataId', - label: 'Field Metadata Id', - targetColumnMap: { - value: 'fieldMetadataId', - }, - description: 'View Field target field', - icon: 'IconTag', - isNullable: false, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.BOOLEAN, - name: 'isVisible', - label: 'Visible', - targetColumnMap: { - value: 'isVisible', - }, - description: 'View Field visibility', - icon: 'IconEye', - isNullable: false, - defaultValue: { value: true }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.NUMBER, - name: 'size', - label: 'Size', - targetColumnMap: { - value: 'size', - }, - description: 'View Field size', - icon: 'IconEye', - isNullable: false, - defaultValue: { value: 0 }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.NUMBER, - name: 'position', - label: 'Position', - targetColumnMap: { - value: 'position', - }, - description: 'View Field position', - icon: 'IconList', - isNullable: false, - defaultValue: { value: 0 }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'view', - label: 'View', - targetColumnMap: {}, - description: 'View Field related view', - icon: 'IconLayoutCollage', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'viewId', - label: 'View Id', - targetColumnMap: { - value: 'viewId', - }, - description: 'View field related view', - icon: 'IconLayoutCollage', - isNullable: false, - }, - ], -}; - -export default viewFieldMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/view-filter.ts b/server/src/workspace/workspace-manager/standard-objects/view-filter.ts deleted file mode 100644 index 57442ed1b..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/view-filter.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const viewFilterMetadata = { - nameSingular: 'viewFilter', - namePlural: 'viewFilters', - labelSingular: 'View Filter', - labelPlural: 'View Filters', - targetTableName: 'viewFilter', - description: '(System) View Filters', - icon: 'IconFilterBolt', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'fieldMetadataId', - label: 'Field Metadata Id', - targetColumnMap: { - value: 'fieldMetadataId', - }, - description: 'View Filter target field', - icon: null, - isNullable: false, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'operand', - label: 'Operand', - targetColumnMap: { - value: 'operand', - }, - description: 'View Filter operand', - icon: null, - isNullable: false, - defaultValue: { value: 'Contains' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'value', - label: 'Value', - targetColumnMap: { - value: 'value', - }, - description: 'View Filter value', - icon: null, - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'displayValue', - label: 'Display Value', - targetColumnMap: { - value: 'displayValue', - }, - description: 'View Filter Display Value', - icon: null, - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'view', - label: 'View', - targetColumnMap: {}, - description: 'View Filter related view', - icon: 'IconLayoutCollage', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'viewId', - label: 'View Id', - targetColumnMap: { - value: 'viewId', - }, - description: 'View Filter related view', - icon: 'IconLayoutCollage', - isNullable: false, - }, - ], -}; - -export default viewFilterMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/view-sort.ts b/server/src/workspace/workspace-manager/standard-objects/view-sort.ts deleted file mode 100644 index 6ddbb58ce..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/view-sort.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const viewSortMetadata = { - nameSingular: 'viewSort', - namePlural: 'viewSorts', - labelSingular: 'View Sort', - labelPlural: 'View Sorts', - targetTableName: 'viewSort', - description: '(System) View Sorts', - icon: 'IconArrowsSort', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'fieldMetadataId', - label: 'Field Metadata Id', - targetColumnMap: { - value: 'fieldMetadataId', - }, - description: 'View Sort target field', - icon: null, - isNullable: false, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'direction', - label: 'Direction', - targetColumnMap: { - value: 'direction', - }, - description: 'View Sort direction', - icon: null, - isNullable: false, - defaultValue: { value: 'asc' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'view', - label: 'View', - targetColumnMap: {}, - description: 'View Sort related view', - icon: 'IconLayoutCollage', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'viewId', - label: 'View Id', - targetColumnMap: { - value: 'viewId', - }, - description: 'View Sort related view', - icon: 'IconLayoutCollage', - isNullable: false, - }, - ], -}; - -export default viewSortMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/view.ts b/server/src/workspace/workspace-manager/standard-objects/view.ts deleted file mode 100644 index 66a5c1d18..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/view.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const viewMetadata = { - nameSingular: 'view', - namePlural: 'views', - labelSingular: 'View', - labelPlural: 'Views', - targetTableName: 'view', - description: '(System) Views', - icon: 'IconLayoutCollage', - isActive: true, - isSystem: true, - fields: [ - { - type: FieldMetadataType.TEXT, - name: 'name', - label: 'Name', - targetColumnMap: { - value: 'name', - }, - description: 'View name', - icon: null, - isNullable: false, - defaultValue: { value: '' }, - }, - { - type: FieldMetadataType.UUID, - name: 'objectMetadataId', - label: 'Object Metadata Id', - targetColumnMap: { - value: 'objectMetadataId', - }, - description: 'View target object', - icon: null, - isNullable: false, - }, - { - type: FieldMetadataType.TEXT, - name: 'type', - label: 'Type', - targetColumnMap: { - value: 'type', - }, - description: 'View type', - icon: null, - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'viewFields', - label: 'View Fields', - targetColumnMap: {}, - description: 'View Fields', - icon: 'IconTag', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'viewSorts', - label: 'View Sorts', - targetColumnMap: {}, - description: 'View Sorts', - icon: 'IconArrowsSort', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'viewFilters', - label: 'View Filters', - targetColumnMap: {}, - description: 'View Filters', - icon: 'IconFilterBolt', - isNullable: true, - }, - ], -}; - -export default viewMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/webhook.ts b/server/src/workspace/workspace-manager/standard-objects/webhook.ts deleted file mode 100644 index 9eca96183..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/webhook.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const webhookMetadata = { - nameSingular: 'webhook', - namePlural: 'webhooks', - labelSingular: 'Webhook', - labelPlural: 'Webhooks', - targetTableName: 'webhook', - description: 'A webhook', - icon: 'IconRobot', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'targetUrl', - label: 'Target Url', - targetColumnMap: { - value: 'targetUrl', - }, - description: 'Webhook target url', - icon: 'IconLink', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'operation', - label: 'Operation', - targetColumnMap: { - value: 'operation', - }, - description: 'Webhook operation', - icon: 'IconCheckbox', - isNullable: false, - defaultValue: { value: '' }, - }, - ], -}; - -export default webhookMetadata; diff --git a/server/src/workspace/workspace-manager/standard-objects/workspace-member.ts b/server/src/workspace/workspace-manager/standard-objects/workspace-member.ts deleted file mode 100644 index a7d167cbd..000000000 --- a/server/src/workspace/workspace-manager/standard-objects/workspace-member.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const workspaceMemberMetadata = { - nameSingular: 'workspaceMember', - namePlural: 'workspaceMembers', - labelSingular: 'Workspace Member', - labelPlural: 'Workspace Members', - targetTableName: 'workspaceMember', - description: 'A workspace member', - icon: 'IconUserCircle', - isActive: true, - isSystem: true, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.FULL_NAME, - name: 'name', - label: 'Name', - targetColumnMap: { - firstName: 'nameFirstName', - lastName: 'nameLastName', - }, - description: 'Workspace member name', - icon: 'IconCircleUser', - isNullable: false, - defaultValue: { firstName: '', lastName: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'colorScheme', - label: 'Color Scheme', - targetColumnMap: { - value: 'colorScheme', - }, - description: 'Preferred color scheme', - icon: 'IconColorSwatch', - isNullable: false, - defaultValue: { value: 'Light' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'locale', - label: 'Language', - targetColumnMap: { - value: 'locale', - }, - description: 'Preferred language', - icon: 'IconLanguage', - isNullable: false, - defaultValue: { value: 'en' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'avatarUrl', - label: 'Avatar Url', - targetColumnMap: { - value: 'avatarUrl', - }, - description: 'Workspace member avatar', - icon: 'IconFileUpload', - isNullable: true, - isSystem: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.UUID, - name: 'userId', - label: 'User Id', - targetColumnMap: { - value: 'userId', - }, - description: 'Associated User Id', - icon: 'IconCircleUsers', - isNullable: false, - isSystem: false, - }, - // Relations - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'authoredActivities', - label: 'Authored activities', - targetColumnMap: {}, - description: 'Activities created by the workspace member', - icon: 'IconCheckbox', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'assignedActivities', - label: 'Assigned activities', - targetColumnMap: {}, - description: 'Activities assigned to the workspace member', - icon: 'IconCheckbox', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'favorites', - label: 'Favorites', - targetColumnMap: {}, - description: 'Favorites linked to the workspace member', - icon: 'IconHeart', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'accountOwnerForCompanies', - label: 'Account Owner For Companies', - targetColumnMap: {}, - description: 'Account owner for companies', - icon: 'IconBriefcase', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'authoredAttachments', - label: 'Authored attachments', - targetColumnMap: {}, - description: 'Attachments created by the workspace member', - icon: 'IconFileImport', - isNullable: true, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.RELATION, - name: 'authoredComments', - label: 'Authored comments', - targetColumnMap: {}, - description: 'Authored comments', - icon: 'IconComment', - isNullable: true, - }, - ], -}; - -export default workspaceMemberMetadata; diff --git a/server/src/workspace/workspace-manager/utils/metadata.parser.ts b/server/src/workspace/workspace-manager/utils/metadata.parser.ts deleted file mode 100644 index bc3148c4f..000000000 --- a/server/src/workspace/workspace-manager/utils/metadata.parser.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity'; -import { BaseObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/base.object-metadata'; - -export class MetadataParser { - static parseMetadata( - metadata: typeof BaseObjectMetadata, - workspaceId: string, - dataSourceId: string, - ) { - const objectMetadata = Reflect.getMetadata('objectMetadata', metadata); - const fieldMetadata = Reflect.getMetadata('fieldMetadata', metadata); - - if (objectMetadata) { - const fields = Object.values(fieldMetadata); - - return { - ...objectMetadata, - workspaceId, - dataSourceId, - fields: fields.map((field: FieldMetadataEntity) => ({ - ...field, - workspaceId, - isSystem: objectMetadata.isSystem || field.isSystem, - defaultValue: field.defaultValue || null, // TODO: use default default value based on field type - options: field.options || null, - })), - }; - } - - return undefined; - } - - static parseAllMetadata( - metadata: (typeof BaseObjectMetadata)[], - workspaceId: string, - dataSourceId: string, - ) { - return metadata.map((_metadata) => - MetadataParser.parseMetadata(_metadata, workspaceId, dataSourceId), - ); - } -} diff --git a/server/src/workspace/workspace-manager/workspace-manager.module.ts b/server/src/workspace/workspace-manager/workspace-manager.module.ts index 00a420ce1..c495de474 100644 --- a/server/src/workspace/workspace-manager/workspace-manager.module.ts +++ b/server/src/workspace/workspace-manager/workspace-manager.module.ts @@ -1,14 +1,10 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { DataSourceModule } from 'src/metadata/data-source/data-source.module'; import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module'; import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module'; -import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module'; import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module'; -import { RelationMetadataModule } from 'src/metadata/relation-metadata/relation-metadata.module'; -import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity'; -import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module'; import { WorkspaceManagerService } from './workspace-manager.service'; @@ -16,14 +12,9 @@ import { WorkspaceManagerService } from './workspace-manager.service'; imports: [ WorkspaceDataSourceModule, WorkspaceMigrationModule, - WorkspaceMigrationRunnerModule, ObjectMetadataModule, DataSourceModule, - RelationMetadataModule, - TypeOrmModule.forFeature( - [FieldMetadataEntity, ObjectMetadataEntity], - 'metadata', - ), + WorkspaceSyncMetadataModule, ], exports: [WorkspaceManagerService], providers: [WorkspaceManagerService], diff --git a/server/src/workspace/workspace-manager/workspace-manager.service.ts b/server/src/workspace/workspace-manager/workspace-manager.service.ts index 7355b6bd4..82d735599 100644 --- a/server/src/workspace/workspace-manager/workspace-manager.service.ts +++ b/server/src/workspace/workspace-manager/workspace-manager.service.ts @@ -1,8 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; -import diff from 'microdiff'; import { DataSourceService } from 'src/metadata/data-source/data-source.service'; import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service'; @@ -12,41 +8,16 @@ import { standardObjectsPrefillData } from 'src/workspace/workspace-manager/stan import { demoObjectsPrefillData } from 'src/workspace/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data'; import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity'; -import { RelationMetadataService } from 'src/metadata/relation-metadata/relation-metadata.service'; -import { standardObjectRelationMetadata } from 'src/workspace/workspace-manager/standard-objects/standard-object-relation-metadata'; -import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; -import { - FieldMetadataEntity, - FieldMetadataType, -} from 'src/metadata/field-metadata/field-metadata.entity'; -import { MetadataParser } from 'src/workspace/workspace-manager/utils/metadata.parser'; -import { WebhookObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/webook.object-metadata'; -import { ApiKeyObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/api-key.object-metadata'; -import { ViewSortObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/view-sort.object-metadata'; -import { - filterIgnoredProperties, - mapObjectMetadataByUniqueIdentifier, -} from 'src/workspace/workspace-manager/utils/sync-metadata.util'; - -import { - basicFieldsMetadata, - standardObjectsMetadata, -} from './standard-objects/standard-object-metadata'; +import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync.metadata.service'; @Injectable() export class WorkspaceManagerService { constructor( private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly workspaceMigrationService: WorkspaceMigrationService, - private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly objectMetadataService: ObjectMetadataService, private readonly dataSourceService: DataSourceService, - private readonly relationMetadataService: RelationMetadataService, - - @InjectRepository(ObjectMetadataEntity, 'metadata') - private readonly objectMetadataRepository: Repository, - @InjectRepository(FieldMetadataEntity, 'metadata') - private readonly fieldMetadataRepository: Repository, + private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, ) {} /** @@ -68,22 +39,14 @@ export class WorkspaceManagerService { await this.setWorkspaceMaxRow(workspaceId, schemaName); - await this.workspaceMigrationService.insertStandardMigrations(workspaceId); - - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata( + dataSourceMetadata.id, workspaceId, ); - const createdObjectMetadata = - await this.createStandardObjectsAndFieldsMetadata( - dataSourceMetadata.id, - workspaceId, - ); - await this.prefillWorkspaceWithStandardObjects( dataSourceMetadata, workspaceId, - createdObjectMetadata, ); } @@ -106,321 +69,12 @@ export class WorkspaceManagerService { await this.setWorkspaceMaxRow(workspaceId, schemaName); - await this.workspaceMigrationService.insertStandardMigrations(workspaceId); - - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata( + dataSourceMetadata.id, workspaceId, ); - const createdObjectMetadata = - await this.createStandardObjectsAndFieldsMetadata( - dataSourceMetadata.id, - workspaceId, - ); - - await this.prefillWorkspaceWithDemoObjects( - dataSourceMetadata, - workspaceId, - createdObjectMetadata, - ); - } - - /** - * - * Create all standard objects and fields metadata for a given workspace - * - * @param dataSourceId - * @param workspaceId - */ - public async createStandardObjectsAndFieldsMetadata( - dataSourceId: string, - workspaceId: string, - ): Promise { - const createdObjectMetadata = await this.objectMetadataService.createMany( - Object.values(standardObjectsMetadata).map((objectMetadata: any) => ({ - ...objectMetadata, - dataSourceId, - workspaceId, - isCustom: false, - isActive: true, - fields: [...basicFieldsMetadata, ...objectMetadata.fields].map( - (field) => ({ - ...field, - workspaceId, - isCustom: false, - isActive: true, - }), - ), - })), - ); - - await this.relationMetadataService.createMany( - Object.values(standardObjectRelationMetadata).map((relationMetadata) => - this.createStandardObjectRelations( - workspaceId, - createdObjectMetadata, - relationMetadata, - ), - ), - ); - - return createdObjectMetadata; - } - - /** - * - * @param workspaceId - * @param createdObjectMetadata - * @param relationMetadata - * @returns Partial - */ - private createStandardObjectRelations( - workspaceId: string, - createdObjectMetadata: ObjectMetadataEntity[], - relationMetadata: any, - ) { - const createdObjectMetadataByNameSingular = createdObjectMetadata.reduce( - (acc, curr) => { - acc[curr.nameSingular] = curr; - - return acc; - }, - {}, - ); - - const fromObjectMetadata = - createdObjectMetadataByNameSingular[ - relationMetadata.fromObjectNameSingular - ]; - const toObjectMetadata = - createdObjectMetadataByNameSingular[ - relationMetadata.toObjectNameSingular - ]; - - if (!fromObjectMetadata) { - throw new Error( - `Could not find created object metadata with - fromObjectNameSingular: ${relationMetadata.fromObjectNameSingular}`, - ); - } - - if (!toObjectMetadata) { - throw new Error( - `Could not find created object metadata with - toObjectNameSingular: ${relationMetadata.toObjectNameSingular}`, - ); - } - - const fromFieldMetadata = createdObjectMetadataByNameSingular[ - relationMetadata.fromObjectNameSingular - ]?.fields.find( - (field: FieldMetadataEntity) => - field.type === FieldMetadataType.RELATION && - field.name === relationMetadata.fromFieldMetadataName, - ); - - const toFieldMetadata = createdObjectMetadataByNameSingular[ - relationMetadata.toObjectNameSingular - ]?.fields.find( - (field: FieldMetadataEntity) => - field.type === FieldMetadataType.RELATION && - field.name === relationMetadata.toFieldMetadataName, - ); - - if (!fromFieldMetadata) { - throw new Error( - `Could not find created field metadata with - fromFieldMetadataName: ${relationMetadata.fromFieldMetadataName} - for object: ${relationMetadata.fromObjectNameSingular}`, - ); - } - - if (!toFieldMetadata) { - throw new Error( - `Could not find created field metadata with - toFieldMetadataName: ${relationMetadata.toFieldMetadataName} - for object: ${relationMetadata.toObjectNameSingular}`, - ); - } - - return { - fromObjectMetadataId: fromObjectMetadata.id, - toObjectMetadataId: toObjectMetadata.id, - workspaceId, - relationType: relationMetadata.type, - fromFieldMetadataId: fromFieldMetadata.id, - toFieldMetadataId: toFieldMetadata.id, - }; - } - - /** - * - * Sync all standard objects and fields metadata for a given workspace and data source - * This will update the metadata if it has changed and generate migrations based on the diff. - * - * @param dataSourceId - * @param workspaceId - */ - public async syncStandardObjectsAndFieldsMetadata( - dataSourceId: string, - workspaceId: string, - ) { - const standardObjects = MetadataParser.parseAllMetadata( - [WebhookObjectMetadata, ApiKeyObjectMetadata, ViewSortObjectMetadata], - workspaceId, - dataSourceId, - ); - const objectsInDB = await this.objectMetadataRepository.find({ - where: { workspaceId, dataSourceId, isCustom: false }, - relations: ['fields'], - }); - - const objectsInDBByName = mapObjectMetadataByUniqueIdentifier(objectsInDB); - const standardObjectsByName = - mapObjectMetadataByUniqueIdentifier(standardObjects); - - const objectsToCreate: ObjectMetadataEntity[] = []; - const objectsToDelete = objectsInDB.filter( - (objectInDB) => !standardObjectsByName[objectInDB.nameSingular], - ); - const objectsToUpdate: Record = {}; - - const fieldsToCreate: FieldMetadataEntity[] = []; - const fieldsToDelete: FieldMetadataEntity[] = []; - const fieldsToUpdate: Record = {}; - - for (const standardObjectName in standardObjectsByName) { - const standardObject = standardObjectsByName[standardObjectName]; - const objectInDB = objectsInDBByName[standardObjectName]; - - if (!objectInDB) { - objectsToCreate.push(standardObject); - continue; - } - - // Deconstruct fields and compare objects and fields independently - const { fields: objectInDBFields, ...objectInDBWithoutFields } = - objectInDB; - const { fields: standardObjectFields, ...standardObjectWithoutFields } = - standardObject; - - const objectPropertiesToIgnore = [ - 'id', - 'createdAt', - 'updatedAt', - 'labelIdentifierFieldMetadataId', - 'imageIdentifierFieldMetadataId', - ]; - const objectDiffWithoutIgnoredProperties = filterIgnoredProperties( - objectInDBWithoutFields, - objectPropertiesToIgnore, - ); - - const fieldPropertiesToIgnore = [ - 'id', - 'createdAt', - 'updatedAt', - 'objectMetadataId', - ]; - const objectInDBFieldsWithoutDefaultFields = Object.fromEntries( - Object.entries(objectInDBFields).map(([key, value]) => { - if (value === null || typeof value !== 'object') { - return [key, value]; - } - - return [key, filterIgnoredProperties(value, fieldPropertiesToIgnore)]; - }), - ); - - // Compare objects - const objectDiff = diff( - objectDiffWithoutIgnoredProperties, - standardObjectWithoutFields, - ); - - // Compare fields - const fieldsDiff = diff( - objectInDBFieldsWithoutDefaultFields, - standardObjectFields, - ); - - for (const diff of objectDiff) { - // We only handle CHANGE here as REMOVE and CREATE are handled earlier. - if (diff.type === 'CHANGE') { - const property = diff.path[0]; - - objectsToUpdate[objectInDB.id] = { - ...objectsToUpdate[objectInDB.id], - [property]: diff.value, - }; - } - } - - for (const diff of fieldsDiff) { - if (diff.type === 'CREATE') { - const fieldName = diff.path[0]; - const fieldMetadata = standardObjectFields[fieldName]; - - fieldsToCreate.push(fieldMetadata); - } - if (diff.type === 'CHANGE') { - const fieldName = diff.path[0]; - const property = diff.path[diff.path.length - 1]; - const fieldMetadata = objectInDBFields[fieldName]; - - fieldsToUpdate[fieldMetadata.id] = { - ...fieldsToUpdate[fieldMetadata.id], - [property]: diff.value, - }; - } - if (diff.type === 'REMOVE') { - const fieldName = diff.path[0]; - const fieldMetadata = objectInDBFields[fieldName]; - - fieldsToDelete.push(fieldMetadata); - } - } - // console.log(standardObjectName + ':objectDiff', objectDiff); - // console.log(standardObjectName + ':fieldsDiff', fieldsDiff); - } - - // TODO: Sync relationMetadata - // NOTE: Relations are handled like any field during the diff, so we ignore the relationMetadata table - // during the diff as it depends on the 2 fieldMetadata that we will compare here. - // However we need to make sure the relationMetadata table is in sync with the fieldMetadata table. - - // TODO: Use transactions - // CREATE OBJECTS - try { - await this.objectMetadataRepository.save(objectsToCreate); - // UPDATE OBJECTS, this is not optimal as we are running n queries here. - for (const [key, value] of Object.entries(objectsToUpdate)) { - await this.objectMetadataRepository.update(key, value); - } - // DELETE OBJECTS - if (objectsToDelete.length > 0) { - await this.objectMetadataRepository.delete( - objectsToDelete.map((object) => object.id), - ); - } - - // CREATE FIELDS - await this.fieldMetadataRepository.save(fieldsToCreate); - // UPDATE FIELDS - for (const [key, value] of Object.entries(fieldsToUpdate)) { - await this.fieldMetadataRepository.update(key, value); - } - // DELETE FIELDS - if (fieldsToDelete.length > 0) { - await this.fieldMetadataRepository.delete( - fieldsToDelete.map((field) => field.id), - ); - } - } catch (e) { - console.error('Sync of standard objects failed with:', e); - } - - // TODO: Create migrations based on diff from above. + await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId); } /** @@ -451,7 +105,6 @@ export class WorkspaceManagerService { private async prefillWorkspaceWithStandardObjects( dataSourceMetadata: DataSourceEntity, workspaceId: string, - createdObjectMetadata: ObjectMetadataEntity[], ) { const workspaceDataSource = await this.workspaceDataSourceService.connectToWorkspaceDataSource( @@ -461,6 +114,10 @@ export class WorkspaceManagerService { if (!workspaceDataSource) { throw new Error('Could not connect to workspace data source'); } + + const createdObjectMetadata = + await this.objectMetadataService.findManyWithinWorkspace(workspaceId); + await standardObjectsPrefillData( workspaceDataSource, dataSourceMetadata.schema, @@ -478,7 +135,6 @@ export class WorkspaceManagerService { private async prefillWorkspaceWithDemoObjects( dataSourceMetadata: DataSourceEntity, workspaceId: string, - createdObjectMetadata: ObjectMetadataEntity[], ) { const workspaceDataSource = await this.workspaceDataSourceService.connectToWorkspaceDataSource( @@ -489,6 +145,9 @@ export class WorkspaceManagerService { throw new Error('Could not connect to workspace data source'); } + const createdObjectMetadata = + await this.objectMetadataService.findManyWithinWorkspace(workspaceId); + await demoObjectsPrefillData( workspaceDataSource, dataSourceMetadata.schema, diff --git a/server/src/workspace/workspace-manager/commands/sync-workspace-metadata.command.ts b/server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts similarity index 73% rename from server/src/workspace/workspace-manager/commands/sync-workspace-metadata.command.ts rename to server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts index f13cf4be1..3bab78c72 100644 --- a/server/src/workspace/workspace-manager/commands/sync-workspace-metadata.command.ts +++ b/server/src/workspace/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts @@ -1,7 +1,7 @@ import { Command, CommandRunner, Option } from 'nest-commander'; import { DataSourceService } from 'src/metadata/data-source/data-source.service'; -import { WorkspaceManagerService } from 'src/workspace/workspace-manager/workspace-manager.service'; +import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync.metadata.service'; // TODO: implement dry-run interface RunWorkspaceMigrationsOptions { @@ -14,7 +14,7 @@ interface RunWorkspaceMigrationsOptions { }) export class SyncWorkspaceMetadataCommand extends CommandRunner { constructor( - private readonly workspaceManagerService: WorkspaceManagerService, + private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, private readonly dataSourceService: DataSourceService, ) { super(); @@ -29,9 +29,7 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( options.workspaceId, ); - - // TODO: This solution could be improved, using a diff for example, we should not have to delete all metadata and recreate them. - await this.workspaceManagerService.syncStandardObjectsAndFieldsMetadata( + await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata( dataSourceMetadata.id, options.workspaceId, ); diff --git a/server/src/workspace/workspace-manager/commands/workspace-manager-commands.module.ts b/server/src/workspace/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module.ts similarity index 53% rename from server/src/workspace/workspace-manager/commands/workspace-manager-commands.module.ts rename to server/src/workspace/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module.ts index 501462ad0..64747784d 100644 --- a/server/src/workspace/workspace-manager/commands/workspace-manager-commands.module.ts +++ b/server/src/workspace/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module.ts @@ -1,12 +1,12 @@ import { Module } from '@nestjs/common'; -import { WorkspaceManagerModule } from 'src/workspace/workspace-manager/workspace-manager.module'; import { DataSourceModule } from 'src/metadata/data-source/data-source.module'; +import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module'; import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command'; @Module({ - imports: [WorkspaceManagerModule, DataSourceModule], + imports: [WorkspaceSyncMetadataModule, DataSourceModule], providers: [SyncWorkspaceMetadataCommand], }) -export class WorkspaceManagerCommandsModule {} +export class WorkspaceSyncMetadataCommandsModule {} diff --git a/server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts b/server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts new file mode 100644 index 000000000..6524bc5b4 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/decorators/metadata.decorator.ts @@ -0,0 +1,183 @@ +import camelCase from 'lodash.camelcase'; +import 'reflect-metadata'; + +import { FieldMetadataDefaultValue } from 'src/metadata/field-metadata/interfaces/field-metadata-default-value.interface'; + +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { generateTargetColumnMap } from 'src/metadata/field-metadata/utils/generate-target-column-map.util'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { generateDefaultValue } from 'src/metadata/field-metadata/utils/generate-default-value'; + +export type FieldMetadataDecorator = { + type: FieldMetadataType; + label: string; + description?: string | null; + icon?: string | null; + defaultValue?: FieldMetadataDefaultValue | null; + joinColumn?: string; +}; + +export type ObjectMetadataDecorator = { + namePlural: string; + labelSingular: string; + labelPlural: string; + description?: string | null; + icon?: string | null; +}; + +export type RelationMetadataDecorator = { + type: RelationMetadataType; + objectName: string; + inverseSideFieldName?: string; +}; + +function convertClassNameToObjectMetadataName(name: string): string { + const classSuffix = 'ObjectMetadata'; + let objectName = camelCase(name); + + if (objectName.endsWith(classSuffix)) { + objectName = objectName.slice(0, -classSuffix.length); + } + + return objectName; +} + +export function ObjectMetadata( + metadata: ObjectMetadataDecorator, +): ClassDecorator { + return (target) => { + const isSystem = Reflect.getMetadata('isSystem', target) || false; + + const objectName = convertClassNameToObjectMetadataName(target.name); + + Reflect.defineMetadata( + 'objectMetadata', + { + nameSingular: objectName, + ...metadata, + targetTableName: objectName, + isSystem, + isCustom: false, + description: metadata.description ?? null, + icon: metadata.icon ?? null, + }, + target, + ); + }; +} + +export function IsNullable() { + return function (target: object, fieldKey: string) { + Reflect.defineMetadata('isNullable', true, target, fieldKey); + }; +} + +export function IsSystem() { + return function (target: object, fieldKey?: string) { + if (fieldKey) { + Reflect.defineMetadata('isSystem', true, target, fieldKey); + } else { + Reflect.defineMetadata('isSystem', true, target); + } + }; +} + +export function FieldMetadata( + metadata: FieldMetadataDecorator, +): PropertyDecorator { + return (target: object, fieldKey: string) => { + const existingFieldMetadata = + Reflect.getMetadata('fieldMetadata', target.constructor) || {}; + + const isNullable = + Reflect.getMetadata('isNullable', target, fieldKey) || false; + + const isSystem = Reflect.getMetadata('isSystem', target, fieldKey) || false; + + const { joinColumn, ...fieldMetadata } = metadata; + + Reflect.defineMetadata( + 'fieldMetadata', + { + ...existingFieldMetadata, + [fieldKey]: generateFieldMetadata( + fieldMetadata, + fieldKey, + isNullable, + isSystem, + ), + ...(joinColumn && fieldMetadata.type === FieldMetadataType.RELATION + ? { + [joinColumn]: generateFieldMetadata( + { + ...fieldMetadata, + type: FieldMetadataType.UUID, + label: `${fieldMetadata.label} id (foreign key)`, + description: `${fieldMetadata.description} id foreign key`, + defaultValue: null, + }, + joinColumn, + isNullable, + true, + ), + } + : {}), + }, + target.constructor, + ); + }; +} + +function generateFieldMetadata( + metadata: FieldMetadataDecorator, + fieldKey: string, + isNullable: boolean, + isSystem: boolean, +) { + const targetColumnMap = JSON.stringify( + generateTargetColumnMap(metadata.type, false, fieldKey), + ); + const defaultValue = + metadata.defaultValue ?? generateDefaultValue(metadata.type); + + return { + name: fieldKey, + ...metadata, + targetColumnMap: targetColumnMap, + isNullable, + isSystem, + isCustom: false, + options: null, // TODO: handle options + stringify for the diff. + description: metadata.description ?? null, + icon: metadata.icon ?? null, + defaultValue: defaultValue ? JSON.stringify(defaultValue) : null, + }; +} + +export function RelationMetadata( + metadata: RelationMetadataDecorator, +): PropertyDecorator { + return (target: object, fieldKey: string) => { + const existingRelationMetadata = + Reflect.getMetadata('relationMetadata', target.constructor) || []; + + const objectName = convertClassNameToObjectMetadataName( + target.constructor.name, + ); + + Reflect.defineMetadata( + 'relationMetadata', + [ + ...existingRelationMetadata, + { + type: metadata.type, + fromObjectNameSingular: objectName, + toObjectNameSingular: metadata.objectName, + fromFieldMetadataName: fieldKey, + toFieldMetadataName: metadata.inverseSideFieldName ?? objectName, + }, + ], + target.constructor, + ); + }; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata.ts new file mode 100644 index 000000000..0fdcbeca0 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata.ts @@ -0,0 +1,47 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + FieldMetadata, + IsSystem, + IsNullable, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'activityTargets', + labelSingular: 'Activity Target', + labelPlural: 'Activity Targets', + description: 'An activity target', + icon: 'IconCheckbox', +}) +@IsSystem() +export class ActivityTargetObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Activity', + description: 'ActivityTarget activity', + icon: 'IconNotes', + joinColumn: 'activityId', + }) + activity: object; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Person', + description: 'ActivityTarget person', + icon: 'IconUser', + joinColumn: 'personId', + }) + @IsNullable() + person: object; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Company', + description: 'ActivityTarget company', + icon: 'IconBuildingSkyscraper', + joinColumn: 'companyId', + }) + @IsNullable() + company: object; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata.ts new file mode 100644 index 000000000..074c6759e --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata.ts @@ -0,0 +1,128 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + IsNullable, + FieldMetadata, + RelationMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'activities', + labelSingular: 'Activity', + labelPlural: 'Activities', + description: 'An activity', + icon: 'IconCheckbox', +}) +@IsSystem() +export class ActivityObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Title', + description: 'Activity title', + icon: 'IconNotes', + }) + @IsNullable() + title: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Body', + description: 'Activity body', + icon: 'IconList', + }) + @IsNullable() + body: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Type', + description: 'Activity type', + icon: 'IconCheckbox', + defaultValue: { value: 'Note' }, + }) + type: string; + + @FieldMetadata({ + type: FieldMetadataType.DATE_TIME, + label: 'Reminder Date', + description: 'Activity reminder date', + icon: 'IconCalendarEvent', + }) + @IsNullable() + reminderAt: Date; + + @FieldMetadata({ + type: FieldMetadataType.DATE_TIME, + label: 'Due Date', + description: 'Activity due date', + icon: 'IconCalendarEvent', + }) + @IsNullable() + dueAt: Date; + + @FieldMetadata({ + type: FieldMetadataType.DATE_TIME, + label: 'Completion Date', + description: 'Activity completion date', + icon: 'IconCheck', + }) + @IsNullable() + completedAt: Date; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Targets', + description: 'Activity targets', + icon: 'IconCheckbox', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'activityTarget', + }) + activityTargets: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Attachments', + description: 'Activity attachments', + icon: 'IconFileImport', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'attachment', + }) + attachments: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Comments', + description: 'Activity comments', + icon: 'IconComment', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'comment', + }) + comments: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Author', + description: 'Activity author', + icon: 'IconUserCircle', + joinColumn: 'authorId', + }) + author: object; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Assignee', + description: 'Acitivity assignee', + icon: 'IconUserCircle', + joinColumn: 'assigneeId', + }) + assignee: object; +} diff --git a/server/src/workspace/workspace-manager/standard-objects/api-key.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/api-key.object-metadata.ts similarity index 80% rename from server/src/workspace/workspace-manager/standard-objects/api-key.object-metadata.ts rename to server/src/workspace/workspace-sync-metadata/standard-objects/api-key.object-metadata.ts index 3b5eb45ad..6a3235011 100644 --- a/server/src/workspace/workspace-manager/standard-objects/api-key.object-metadata.ts +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/api-key.object-metadata.ts @@ -1,17 +1,17 @@ import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; import { + ObjectMetadata, FieldMetadata, IsNullable, IsSystem, - ObjectMetadata, -} from 'src/workspace/workspace-manager/decorators/metadata.decorator'; -import { BaseObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/base.object-metadata'; +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; @ObjectMetadata({ namePlural: 'apiKeys', labelSingular: 'Api Key', labelPlural: 'Api Keys', - description: 'A api key', + description: 'An api key', icon: 'IconRobot', }) @IsSystem() diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata.ts new file mode 100644 index 000000000..25d042d6f --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata.ts @@ -0,0 +1,80 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, + IsNullable, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'attachments', + labelSingular: 'Attachment', + labelPlural: 'Attachments', + description: 'An attachment', + icon: 'IconFileImport', +}) +@IsSystem() +export class AttachmentObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Name', + description: 'Attachment name', + icon: 'IconFileUpload', + }) + name: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Full path', + description: 'Attachment full path', + icon: 'IconLink', + }) + fullPath: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Type', + description: 'Attachment type', + icon: 'IconList', + }) + type: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Author', + description: 'Attachment author', + icon: 'IconCircleUser', + joinColumn: 'authorId', + }) + author: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Activity', + description: 'Attachment activity', + icon: 'IconNotes', + joinColumn: 'activityId', + }) + activity: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Person', + description: 'Attachment person', + icon: 'IconUser', + joinColumn: 'personId', + }) + @IsNullable() + person: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Company', + description: 'Attachment company', + icon: 'IconBuildingSkyscraper', + joinColumn: 'companyId', + }) + @IsNullable() + company: string; +} diff --git a/server/src/workspace/workspace-manager/standard-objects/base.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata.ts similarity index 89% rename from server/src/workspace/workspace-manager/standard-objects/base.object-metadata.ts rename to server/src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata.ts index 9b23cbb76..e7f791d23 100644 --- a/server/src/workspace/workspace-manager/standard-objects/base.object-metadata.ts +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata.ts @@ -2,7 +2,7 @@ import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.en import { FieldMetadata, IsSystem, -} from 'src/workspace/workspace-manager/decorators/metadata.decorator'; +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; export abstract class BaseObjectMetadata { @FieldMetadata({ @@ -22,7 +22,6 @@ export abstract class BaseObjectMetadata { icon: 'IconCalendar', defaultValue: { type: 'now' }, }) - @IsSystem() createdAt: Date; @FieldMetadata({ diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata.ts new file mode 100644 index 000000000..d99336abb --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata.ts @@ -0,0 +1,44 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'comments', + labelSingular: 'Comment', + labelPlural: 'Comments', + description: 'A comment', + icon: 'IconMessageCircle', +}) +@IsSystem() +export class CommentObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Body', + description: 'Comment body', + icon: 'IconLink', + defaultValue: { value: '' }, + }) + body: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Author', + description: 'Comment author', + icon: 'IconCircleUser', + joinColumn: 'authorId', + }) + author: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Activity', + description: 'Comment activity', + icon: 'IconNotes', + joinColumn: 'activityId', + }) + activity: string; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata.ts new file mode 100644 index 000000000..a6e50efb2 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata.ts @@ -0,0 +1,164 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + ObjectMetadata, + FieldMetadata, + IsNullable, + RelationMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'companies', + labelSingular: 'Company', + labelPlural: 'Companies', + description: 'A company', + icon: 'IconBuildingSkyscraper', +}) +export class CompanyObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Name', + description: 'The company name', + icon: 'IconBuildingSkyscraper', + }) + name: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Domain Name', + description: + 'The company website URL. We use this url to fetch the company icon', + icon: 'IconLink', + }) + @IsNullable() + domainName?: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Address', + description: 'The company address', + icon: 'IconMap', + }) + @IsNullable() + address: string; + + @FieldMetadata({ + type: FieldMetadataType.NUMBER, + label: 'Employees', + description: 'Number of employees in the company', + icon: 'IconUsers', + }) + @IsNullable() + employees: number; + + @FieldMetadata({ + type: FieldMetadataType.LINK, + label: 'Linkedin', + description: 'The company Linkedin account', + icon: 'IconBrandLinkedin', + }) + @IsNullable() + linkedinLink: string; + + @FieldMetadata({ + type: FieldMetadataType.LINK, + label: 'X', + description: 'The company Twitter/X account', + icon: 'IconBrandX', + }) + @IsNullable() + xLink: string; + + @FieldMetadata({ + type: FieldMetadataType.CURRENCY, + label: 'ARR', + description: + 'Annual Recurring Revenue: The actual or estimated annual revenue of the company', + icon: 'IconMoneybag', + }) + @IsNullable() + annualRecurringRevenue: number; + + @FieldMetadata({ + type: FieldMetadataType.BOOLEAN, + label: 'ICP', + description: + 'Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you', + icon: 'IconTarget', + }) + @IsNullable() + idealCustomerProfile: boolean; + + // Relations + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'People', + description: 'People linked to the company.', + icon: 'IconUsers', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'person', + }) + people: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Account Owner', + description: + 'Your team member responsible for managing the company account', + icon: 'IconUserCircle', + joinColumn: 'accountOwnerId', + }) + @IsNullable() + accountOwner: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Activities', + description: 'Activities tied to the company', + icon: 'IconCheckbox', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'activityTarget', + }) + activityTargets: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Opportunities', + description: 'Opportunities linked to the company.', + icon: 'IconTargetArrow', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'opportunity', + }) + opportunities: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Favorites', + description: 'Favorites linked to the company', + icon: 'IconHeart', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'favorite', + }) + favorites: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Attachments', + description: 'Attachments linked to the company.', + icon: 'IconFileImport', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'attachment', + }) + attachments: object[]; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts new file mode 100644 index 000000000..003612307 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata.ts @@ -0,0 +1,54 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'favorites', + labelSingular: 'Favorite', + labelPlural: 'Favorites', + description: 'A favorite', + icon: 'IconHeart', +}) +@IsSystem() +export class FavoriteObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.NUMBER, + label: 'Position', + description: 'Favorite position', + icon: 'IconList', + defaultValue: { value: 0 }, + }) + position: number; + + // Relations + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Workspace Member', + description: 'Favorite workspace member', + icon: 'IconCircleUser', + joinColumn: 'workspaceMemberId', + }) + workspaceMember: object; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Person', + description: 'Favorite person', + icon: 'IconUser', + joinColumn: 'personId', + }) + person: object; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Company', + description: 'Favorite company', + icon: 'IconBuildingSkyscraper', + joinColumn: 'companyId', + }) + company: object; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/index.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/index.ts new file mode 100644 index 000000000..944afae8e --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/index.ts @@ -0,0 +1,35 @@ +import { ActivityTargetObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata'; +import { ActivityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata'; +import { ApiKeyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/api-key.object-metadata'; +import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata'; +import { CommentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata'; +import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata'; +import { FavoriteObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata'; +import { OpportunityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata'; +import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata'; +import { PipelineStepObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/pipeline-step.object-metadata'; +import { ViewFieldObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view-field.object-metadata'; +import { ViewFilterObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view-filter.object-metadata'; +import { ViewSortObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view-sort.object-metadata'; +import { ViewObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata'; +import { WebhookObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/webook.object-metadata'; +import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata'; + +export const standardObjectMetadata = [ + ActivityTargetObjectMetadata, + ActivityObjectMetadata, + ApiKeyObjectMetadata, + AttachmentObjectMetadata, + CommentObjectMetadata, + CompanyObjectMetadata, + FavoriteObjectMetadata, + OpportunityObjectMetadata, + PersonObjectMetadata, + PipelineStepObjectMetadata, + ViewFieldObjectMetadata, + ViewFilterObjectMetadata, + ViewSortObjectMetadata, + ViewObjectMetadata, + WebhookObjectMetadata, + WorkspaceMemberObjectMetadata, +]; diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts new file mode 100644 index 000000000..7646f613d --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata.ts @@ -0,0 +1,85 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, + IsNullable, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'opportunities', + labelSingular: 'Opportunity', + labelPlural: 'Opportunities', + description: 'An opportunity', + icon: 'IconTargetArrow', +}) +export class OpportunityObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.CURRENCY, + label: 'Amount', + description: 'Opportunity amount', + icon: 'IconCurrencyDollar', + }) + @IsNullable() + amount: string; + + @FieldMetadata({ + type: FieldMetadataType.DATE_TIME, + label: 'Close date', + description: 'Opportunity close date', + icon: 'IconCalendarEvent', + }) + @IsNullable() + closeDate: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Probability', + description: 'Opportunity probability', + icon: 'IconProgressCheck', + defaultValue: { value: '0' }, + }) + @IsNullable() + probability: string; + + // Relations + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Pipeline Step', + description: 'Opportunity pipeline step', + icon: 'IconKanban', + joinColumn: 'pipelineStepId', + }) + @IsNullable() + pipelineStep: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Point of Contact', + description: 'Opportunity point of contact', + icon: 'IconUser', + joinColumn: 'pointOfContactId', + }) + @IsNullable() + pointOfContact: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Person', + description: 'Opportunity person', + icon: 'IconUser', + joinColumn: 'personId', + }) + person: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Company', + description: 'Opportunity company', + icon: 'IconBuildingSkyscraper', + joinColumn: 'companyId', + }) + @IsNullable() + company: string; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts new file mode 100644 index 000000000..f087b8072 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts @@ -0,0 +1,164 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + ObjectMetadata, + FieldMetadata, + IsNullable, + RelationMetadata, + IsSystem, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'people', + labelSingular: 'Person', + labelPlural: 'People', + description: 'A person', + icon: 'IconUser', +}) +export class PersonObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.FULL_NAME, + label: 'Name', + description: 'Contact’s name', + icon: 'IconUser', + }) + @IsNullable() + name: string; + + @FieldMetadata({ + type: FieldMetadataType.EMAIL, + label: 'Email', + description: 'Contact’s Email', + icon: 'IconMail', + }) + @IsNullable() + email: string; + + @FieldMetadata({ + type: FieldMetadataType.LINK, + label: 'Linkedin', + description: 'Contact’s Linkedin account', + icon: 'IconBrandLinkedin', + }) + @IsNullable() + linkedinLink: string; + + @FieldMetadata({ + type: FieldMetadataType.LINK, + label: 'X', + description: 'Contact’s X/Twitter account', + icon: 'IconBrandX', + }) + @IsNullable() + xLink: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Job Title', + description: 'Contact’s job title', + icon: 'IconBriefcase', + }) + @IsNullable() + jobTitle: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Phone', + description: 'Contact’s phone number', + icon: 'IconPhone', + }) + @IsNullable() + phone: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'City', + description: 'Contact’s city', + icon: 'IconMap', + }) + @IsNullable() + city: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Avatar', + description: 'Contact’s avatar', + icon: 'IconFileUpload', + }) + @IsSystem() + @IsNullable() + avatarUrl: string; + + // Relations + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Company', + description: 'Contact’s company', + icon: 'IconBuildingSkyscraper', + joinColumn: 'companyId', + }) + @IsNullable() + company: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'POC for Opportunities', + description: 'Point of Contact for Opportunities', + icon: 'IconTargetArrow', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'opportunity', + inverseSideFieldName: 'pointOfContact', + }) + pointOfContactForOpportunities: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Activities', + description: 'Activities tied to the contact', + icon: 'IconCheckbox', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'activityTarget', + }) + activityTargets: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Opportunities', + description: 'Opportunities linked to the contact.', + icon: 'IconTargetArrow', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'opportunity', + }) + opportunities: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Favorites', + description: 'Favorites linked to the contact', + icon: 'IconHeart', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'favorite', + }) + favorites: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Attachments', + description: 'Attachments linked to the contact.', + icon: 'IconFileImport', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'attachment', + }) + attachments: object[]; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/pipeline-step.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/pipeline-step.object-metadata.ts new file mode 100644 index 000000000..5bb97977a --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/pipeline-step.object-metadata.ts @@ -0,0 +1,62 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + ObjectMetadata, + FieldMetadata, + IsNullable, + IsSystem, + RelationMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'pipelineSteps', + labelSingular: 'Pipeline Step', + labelPlural: 'Pipeline Steps', + description: 'A pipeline step', + icon: 'IconLayoutKanban', +}) +@IsSystem() +export class PipelineStepObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Name', + description: 'Pipeline Step name', + icon: 'IconCurrencyDollar', + }) + @IsNullable() + name: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Color', + description: 'Pipeline Step color', + icon: 'IconColorSwatch', + }) + @IsNullable() + color: string; + + @FieldMetadata({ + type: FieldMetadataType.NUMBER, + label: 'Position', + description: 'Pipeline Step position', + icon: 'IconHierarchy2', + defaultValue: { value: 0 }, + }) + @IsNullable() + position: number; + + // Relations + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Opportunities', + description: 'Opportunities linked to the step.', + icon: 'IconTargetArrow', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'opportunity', + }) + @IsNullable() + opportunities: object[]; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/view-field.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/view-field.object-metadata.ts new file mode 100644 index 000000000..6f3e12e76 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/view-field.object-metadata.ts @@ -0,0 +1,63 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, + IsNullable, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'viewFields', + labelSingular: 'View Field', + labelPlural: 'View Fields', + description: '(System) View Fields', + icon: 'IconTag', +}) +@IsSystem() +export class ViewFieldObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.UUID, + label: 'Field Metadata Id', + description: 'View Field target field', + icon: 'IconTag', + }) + fieldMetadataId: string; + + @FieldMetadata({ + type: FieldMetadataType.BOOLEAN, + label: 'Visible', + description: 'View Field visibility', + icon: 'IconEye', + defaultValue: { value: true }, + }) + isVisible: boolean; + + @FieldMetadata({ + type: FieldMetadataType.NUMBER, + label: 'Size', + description: 'View Field size', + icon: 'IconEye', + defaultValue: { value: 0 }, + }) + size: number; + + @FieldMetadata({ + type: FieldMetadataType.NUMBER, + label: 'Position', + description: 'View Field position', + icon: 'IconList', + defaultValue: { value: 0 }, + }) + position: number; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'View', + description: 'View Field related view', + icon: 'IconLayoutCollage', + joinColumn: 'viewId', + }) + @IsNullable() + view?: object; +} diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/view-filter.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/view-filter.object-metadata.ts new file mode 100644 index 000000000..dca92c2db --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/view-filter.object-metadata.ts @@ -0,0 +1,63 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, + IsNullable, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'viewFilters', + labelSingular: 'View Filter', + labelPlural: 'View Filters', + description: '(System) View Filters', + icon: 'IconFilterBolt', +}) +@IsSystem() +export class ViewFilterObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.UUID, + label: 'Field Metadata Id', + description: 'View Filter target field', + icon: null, + }) + fieldMetadataId: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Operand', + description: 'View Filter operand', + icon: null, + defaultValue: { value: 'Contains' }, + }) + operand: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Value', + description: 'View Filter value', + icon: null, + defaultValue: { value: '' }, + }) + value: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Display Value', + description: 'View Filter Display Value', + icon: null, + defaultValue: { value: '' }, + }) + displayValue: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'View', + description: 'View Filter related view', + icon: 'IconLayoutCollage', + joinColumn: 'viewId', + }) + @IsNullable() + view: string; +} diff --git a/server/src/workspace/workspace-manager/standard-objects/view-sort.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/view-sort.object-metadata.ts similarity index 63% rename from server/src/workspace/workspace-manager/standard-objects/view-sort.object-metadata.ts rename to server/src/workspace/workspace-sync-metadata/standard-objects/view-sort.object-metadata.ts index 53c790334..2c776811f 100644 --- a/server/src/workspace/workspace-manager/standard-objects/view-sort.object-metadata.ts +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/view-sort.object-metadata.ts @@ -4,8 +4,8 @@ import { FieldMetadata, IsNullable, IsSystem, -} from 'src/workspace/workspace-manager/decorators/metadata.decorator'; -import { BaseObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/base.object-metadata'; +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; @ObjectMetadata({ namePlural: 'viewSorts', @@ -20,17 +20,26 @@ export class ViewSortObjectMetadata extends BaseObjectMetadata { type: FieldMetadataType.UUID, label: 'Field Metadata Id', description: 'View Sort target field', - icon: null, + icon: 'IconTag', }) fieldMetadataId: string; - // TODO: We could create a relation decorator but let's keep it simple for now. + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Direction', + description: 'View Sort direction', + icon: null, + defaultValue: { value: 'asc' }, + }) + direction: string; + @FieldMetadata({ type: FieldMetadataType.RELATION, label: 'View', description: 'View Sort related view', icon: 'IconLayoutCollage', + joinColumn: 'viewId', }) @IsNullable() - view?: object; + view: string; } diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata.ts new file mode 100644 index 000000000..ac270cf83 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata.ts @@ -0,0 +1,81 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, + RelationMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'views', + labelSingular: 'View', + labelPlural: 'Views', + description: '(System) Views', + icon: 'IconLayoutCollage', +}) +@IsSystem() +export class ViewObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Name', + description: 'View name', + icon: null, + defaultValue: { value: '' }, + }) + name: string; + + @FieldMetadata({ + type: FieldMetadataType.UUID, + label: 'Object Metadata Id', + description: 'View target object', + icon: null, + }) + objectMetadataId: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Type', + description: 'View type', + icon: null, + defaultValue: { value: 'table' }, + }) + type: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'View Fields', + description: 'View Fields', + icon: 'IconTag', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'viewField', + }) + viewFields: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'View Filters', + description: 'View Filters', + icon: 'IconFilterBolt', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'viewFilter', + }) + viewFilters: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'View Sorts', + description: 'View Sorts', + icon: 'IconArrowsSort', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'viewSort', + }) + viewSorts: object[]; +} diff --git a/server/src/workspace/workspace-manager/standard-objects/webook.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/webook.object-metadata.ts similarity index 80% rename from server/src/workspace/workspace-manager/standard-objects/webook.object-metadata.ts rename to server/src/workspace/workspace-sync-metadata/standard-objects/webook.object-metadata.ts index 3a2448e2e..08cce9a24 100644 --- a/server/src/workspace/workspace-manager/standard-objects/webook.object-metadata.ts +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/webook.object-metadata.ts @@ -1,10 +1,10 @@ import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; import { - FieldMetadata, - IsSystem, ObjectMetadata, -} from 'src/workspace/workspace-manager/decorators/metadata.decorator'; -import { BaseObjectMetadata } from 'src/workspace/workspace-manager/standard-objects/base.object-metadata'; + IsSystem, + FieldMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; @ObjectMetadata({ namePlural: 'webhooks', diff --git a/server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts b/server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts new file mode 100644 index 000000000..f6e5e3dd0 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts @@ -0,0 +1,142 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, + IsNullable, + RelationMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +@ObjectMetadata({ + namePlural: 'workspaceMembers', + labelSingular: 'Workspace Member', + labelPlural: 'Workspace Members', + description: 'A workspace member', + icon: 'IconUserCircle', +}) +@IsSystem() +export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.FULL_NAME, + label: 'Name', + description: 'Workspace member name', + icon: 'IconCircleUser', + }) + name: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Color Scheme', + description: 'Preferred color scheme', + icon: 'IconColorSwatch', + defaultValue: { value: 'Light' }, + }) + colorScheme: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Language', + description: 'Preferred language', + icon: 'IconLanguage', + defaultValue: { value: 'en' }, + }) + locale: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Avatar Url', + description: 'Workspace member avatar', + icon: 'IconFileUpload', + defaultValue: { value: '' }, + }) + @IsNullable() + avatarUrl: string; + + @FieldMetadata({ + type: FieldMetadataType.UUID, + label: 'User Id', + description: 'Associated User Id', + icon: 'IconCircleUsers', + }) + userId: string; + + // Relations + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Authored activities', + description: 'Activities created by the workspace member', + icon: 'IconCheckbox', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'activity', + inverseSideFieldName: 'author', + }) + authoredActivities: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Assigned activities', + description: 'Activities assigned to the workspace member', + icon: 'IconCheckbox', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'activity', + inverseSideFieldName: 'assignee', + }) + assignedActivities: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Favorites', + description: 'Favorites linked to the workspace member', + icon: 'IconHeart', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'favorite', + }) + favorites: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Account Owner For Companies', + description: 'Account owner for companies', + icon: 'IconBriefcase', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'company', + inverseSideFieldName: 'accountOwner', + }) + accountOwnerForCompanies: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Authored attachments', + description: 'Attachments created by the workspace member', + icon: 'IconFileImport', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'attachment', + inverseSideFieldName: 'author', + }) + authoredAttachments: object[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Authored comments', + description: 'Authored comments', + icon: 'IconComment', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'comment', + inverseSideFieldName: 'author', + }) + authoredComments: object[]; +} diff --git a/server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts b/server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts new file mode 100644 index 000000000..497b2755e --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts @@ -0,0 +1,109 @@ +import assert from 'assert'; + +import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; + +export class MetadataParser { + static parseMetadata( + metadata: typeof BaseObjectMetadata, + workspaceId: string, + dataSourceId: string, + ) { + const objectMetadata = Reflect.getMetadata('objectMetadata', metadata); + const fieldMetadata = Reflect.getMetadata('fieldMetadata', metadata); + + if (objectMetadata) { + const fields = Object.values(fieldMetadata); + + return { + ...objectMetadata, + workspaceId, + dataSourceId, + fields: fields.map((field: FieldMetadataEntity) => ({ + ...field, + workspaceId, + isSystem: objectMetadata.isSystem || field.isSystem, + defaultValue: field.defaultValue || null, + options: field.options || null, + })), + }; + } + + return undefined; + } + + static parseAllMetadata( + metadata: (typeof BaseObjectMetadata)[], + workspaceId: string, + dataSourceId: string, + ) { + return metadata.map((_metadata) => + MetadataParser.parseMetadata(_metadata, workspaceId, dataSourceId), + ); + } + + static parseRelationMetadata( + metadata: typeof BaseObjectMetadata, + workspaceId: string, + objectMetadataFromDB: Record, + ) { + const objectMetadata = Reflect.getMetadata('objectMetadata', metadata); + const relationMetadata = Reflect.getMetadata('relationMetadata', metadata); + + if (!relationMetadata) return []; + + return relationMetadata.map((relation) => { + const fromObjectMetadata = + objectMetadataFromDB[relation.fromObjectNameSingular]; + assert( + fromObjectMetadata, + `Object ${relation.fromObjectNameSingular} not found in DB + for relation defined in class ${objectMetadata.nameSingular}`, + ); + const toObjectMetadata = + objectMetadataFromDB[relation.toObjectNameSingular]; + assert( + toObjectMetadata, + `Object ${relation.toObjectNameSingular} not found in DB + for relation defined in class ${objectMetadata.nameSingular}`, + ); + const fromFieldMetadata = + fromObjectMetadata?.fields[relation.fromFieldMetadataName]; + assert( + fromFieldMetadata, + `Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular} + for relation defined in class ${objectMetadata.nameSingular}`, + ); + const toFieldMetadata = + toObjectMetadata?.fields[relation.toFieldMetadataName]; + assert( + toFieldMetadata, + `Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular} + for relation defined in class ${objectMetadata.nameSingular}`, + ); + return { + relationType: relation.type, + fromObjectMetadataId: fromObjectMetadata?.id, + toObjectMetadataId: toObjectMetadata?.id, + fromFieldMetadataId: fromFieldMetadata?.id, + toFieldMetadataId: toFieldMetadata?.id, + workspaceId, + }; + }); + } + + static parseAllRelations( + metadata: (typeof BaseObjectMetadata)[], + workspaceId: string, + objectMetadataFromDB: Record, + ) { + return metadata.flatMap((_metadata) => + MetadataParser.parseRelationMetadata( + _metadata, + workspaceId, + objectMetadataFromDB, + ), + ); + } +} diff --git a/server/src/workspace/workspace-manager/utils/sync-metadata.util.spec.ts b/server/src/workspace/workspace-sync-metadata/utils/sync-metadata.util.spec.ts similarity index 100% rename from server/src/workspace/workspace-manager/utils/sync-metadata.util.spec.ts rename to server/src/workspace/workspace-sync-metadata/utils/sync-metadata.util.spec.ts diff --git a/server/src/workspace/workspace-manager/utils/sync-metadata.util.ts b/server/src/workspace/workspace-sync-metadata/utils/sync-metadata.util.ts similarity index 64% rename from server/src/workspace/workspace-manager/utils/sync-metadata.util.ts rename to server/src/workspace/workspace-sync-metadata/utils/sync-metadata.util.ts index d2000bbf1..4d4747e11 100644 --- a/server/src/workspace/workspace-manager/utils/sync-metadata.util.ts +++ b/server/src/workspace/workspace-sync-metadata/utils/sync-metadata.util.ts @@ -11,9 +11,12 @@ import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metada export const filterIgnoredProperties = ( obj: any, propertiesToIgnore: string[], + mapFunction?: (value: any) => any, ) => { return Object.fromEntries( - Object.entries(obj).filter(([key]) => !propertiesToIgnore.includes(key)), + Object.entries(obj) + .filter(([key]) => !propertiesToIgnore.includes(key)) + .map(([key, value]) => [key, mapFunction ? mapFunction(value) : value]), ); }; @@ -41,3 +44,22 @@ export const mapObjectMetadataByUniqueIdentifier = ( return acc; }, {}); }; + +export const convertStringifiedFieldsToJSON = (fieldMetadata) => { + if (fieldMetadata.targetColumnMap) { + fieldMetadata.targetColumnMap = JSON.parse( + fieldMetadata.targetColumnMap as unknown as string, + ); + } + if (fieldMetadata.defaultValue) { + fieldMetadata.defaultValue = JSON.parse( + fieldMetadata.defaultValue as unknown as string, + ); + } + if (fieldMetadata.options) { + fieldMetadata.options = JSON.parse( + fieldMetadata.options as unknown as string, + ); + } + return fieldMetadata; +}; diff --git a/server/src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module.ts b/server/src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module.ts new file mode 100644 index 000000000..6b993f97c --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module'; +import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module'; +import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync.metadata.service'; + +@Module({ + imports: [ + WorkspaceMigrationModule, + WorkspaceMigrationRunnerModule, + TypeOrmModule.forFeature( + [ + FieldMetadataEntity, + ObjectMetadataEntity, + RelationMetadataEntity, + WorkspaceMigrationEntity, + ], + 'metadata', + ), + ], + exports: [WorkspaceSyncMetadataService], + providers: [WorkspaceSyncMetadataService], +}) +export class WorkspaceSyncMetadataModule {} diff --git a/server/src/workspace/workspace-sync-metadata/workspace-sync.metadata.service.ts b/server/src/workspace/workspace-sync-metadata/workspace-sync.metadata.service.ts new file mode 100644 index 000000000..e73e41f33 --- /dev/null +++ b/server/src/workspace/workspace-sync-metadata/workspace-sync.metadata.service.ts @@ -0,0 +1,435 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import diff from 'microdiff'; +import { Repository } from 'typeorm'; +import camelCase from 'lodash.camelcase'; + +import { + FieldMetadataEntity, + FieldMetadataType, +} from 'src/metadata/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; +import { + RelationMetadataEntity, + RelationMetadataType, +} from 'src/metadata/relation-metadata/relation-metadata.entity'; +import { MetadataParser } from 'src/workspace/workspace-sync-metadata/utils/metadata.parser'; +import { + mapObjectMetadataByUniqueIdentifier, + filterIgnoredProperties, + convertStringifiedFieldsToJSON, +} from 'src/workspace/workspace-sync-metadata/utils/sync-metadata.util'; +import { standardObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects'; +import { + WorkspaceMigrationColumnActionType, + WorkspaceMigrationColumnRelation, + WorkspaceMigrationEntity, + WorkspaceMigrationTableAction, +} from 'src/metadata/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; +import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service'; + +@Injectable() +export class WorkspaceSyncMetadataService { + constructor( + private readonly workspaceMigrationFactory: WorkspaceMigrationFactory, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + + @InjectRepository(ObjectMetadataEntity, 'metadata') + private readonly objectMetadataRepository: Repository, + @InjectRepository(FieldMetadataEntity, 'metadata') + private readonly fieldMetadataRepository: Repository, + @InjectRepository(RelationMetadataEntity, 'metadata') + private readonly relationMetadataRepository: Repository, + @InjectRepository(WorkspaceMigrationEntity, 'metadata') + private readonly workspaceMigrationRepository: Repository, + ) {} + + /** + * + * Sync all standard objects and fields metadata for a given workspace and data source + * This will update the metadata if it has changed and generate migrations based on the diff. + * + * @param dataSourceId + * @param workspaceId + */ + public async syncStandardObjectsAndFieldsMetadata( + dataSourceId: string, + workspaceId: string, + ) { + const standardObjects = MetadataParser.parseAllMetadata( + standardObjectMetadata, + workspaceId, + dataSourceId, + ); + + try { + const objectsInDB = await this.objectMetadataRepository.find({ + where: { workspaceId, dataSourceId, isCustom: false }, + relations: ['fields'], + }); + + const objectsInDBByName = + mapObjectMetadataByUniqueIdentifier(objectsInDB); + const standardObjectsByName = + mapObjectMetadataByUniqueIdentifier(standardObjects); + + const objectsToCreate: ObjectMetadataEntity[] = []; + const objectsToDelete = objectsInDB.filter( + (objectInDB) => !standardObjectsByName[objectInDB.nameSingular], + ); + const objectsToUpdate: Record = {}; + + const fieldsToCreate: FieldMetadataEntity[] = []; + const fieldsToDelete: FieldMetadataEntity[] = []; + const fieldsToUpdate: Record = {}; + + for (const standardObjectName in standardObjectsByName) { + const standardObject = standardObjectsByName[standardObjectName]; + const objectInDB = objectsInDBByName[standardObjectName]; + + if (!objectInDB) { + objectsToCreate.push(standardObject); + continue; + } + + // Deconstruct fields and compare objects and fields independently + const { fields: objectInDBFields, ...objectInDBWithoutFields } = + objectInDB; + const { fields: standardObjectFields, ...standardObjectWithoutFields } = + standardObject; + + const objectPropertiesToIgnore = [ + 'id', + 'createdAt', + 'updatedAt', + 'labelIdentifierFieldMetadataId', + 'imageIdentifierFieldMetadataId', + 'isActive', + ]; + const objectDiffWithoutIgnoredProperties = filterIgnoredProperties( + objectInDBWithoutFields, + objectPropertiesToIgnore, + ); + + const fieldPropertiesToIgnore = [ + 'id', + 'createdAt', + 'updatedAt', + 'objectMetadataId', + 'isActive', + ]; + const objectInDBFieldsWithoutDefaultFields = Object.fromEntries( + Object.entries(objectInDBFields).map(([key, value]) => { + if (value === null || typeof value !== 'object') { + return [key, value]; + } + return [ + key, + filterIgnoredProperties( + value, + fieldPropertiesToIgnore, + (property) => { + if (property !== null && typeof property === 'object') { + return JSON.stringify(property); + } + return property; + }, + ), + ]; + }), + ); + + // Compare objects + const objectDiff = diff( + objectDiffWithoutIgnoredProperties, + standardObjectWithoutFields, + ); + + // Compare fields + const fieldsDiff = diff( + objectInDBFieldsWithoutDefaultFields, + standardObjectFields, + ); + + for (const diff of objectDiff) { + // We only handle CHANGE here as REMOVE and CREATE are handled earlier. + if (diff.type === 'CHANGE') { + const property = diff.path[0]; + objectsToUpdate[objectInDB.id] = { + ...objectsToUpdate[objectInDB.id], + [property]: diff.value, + }; + } + } + + for (const diff of fieldsDiff) { + const fieldName = diff.path[0]; + if (diff.type === 'CREATE') + fieldsToCreate.push({ + ...standardObjectFields[fieldName], + objectMetadataId: objectInDB.id, + }); + if (diff.type === 'REMOVE' && diff.path.length === 1) + fieldsToDelete.push(objectInDBFields[fieldName]); + if (diff.type === 'CHANGE') { + const property = diff.path[diff.path.length - 1]; + fieldsToUpdate[objectInDBFields[fieldName].id] = { + ...fieldsToUpdate[objectInDBFields[fieldName].id], + [property]: diff.value, + }; + } + } + } + + // CREATE OBJECTS + await this.objectMetadataRepository.save( + objectsToCreate.map((object) => ({ + ...object, + isActive: true, + fields: Object.values(object.fields).map((field) => ({ + ...convertStringifiedFieldsToJSON(field), + isActive: true, + })), + })), + ); + // UPDATE OBJECTS, this is not optimal as we are running n queries here. + for (const [key, value] of Object.entries(objectsToUpdate)) { + await this.objectMetadataRepository.update(key, value); + } + // DELETE OBJECTS + if (objectsToDelete.length > 0) { + await this.objectMetadataRepository.delete( + objectsToDelete.map((object) => object.id), + ); + } + + // CREATE FIELDS + await this.fieldMetadataRepository.save( + fieldsToCreate.map((field) => convertStringifiedFieldsToJSON(field)), + ); + // UPDATE FIELDS + for (const [key, value] of Object.entries(fieldsToUpdate)) { + await this.fieldMetadataRepository.update( + key, + convertStringifiedFieldsToJSON(value), + ); + } + // DELETE FIELDS + // TODO: handle relation fields deletion. We need to delete the relation metadata first due to the DB constraint. + const fieldsToDeleteWithoutRelationType = fieldsToDelete.filter( + (field) => field.type !== FieldMetadataType.RELATION, + ); + if (fieldsToDeleteWithoutRelationType.length > 0) { + await this.fieldMetadataRepository.delete( + fieldsToDeleteWithoutRelationType.map((field) => field.id), + ); + } + + // Generate migrations + await this.generateMigrationsFromSync( + objectsToCreate, + objectsToDelete, + fieldsToCreate, + fieldsToDelete, + ); + + // We run syncRelationMetadata after everything to ensure that all objects and fields are + // in the DB before creating relations. + await this.syncRelationMetadata(workspaceId, dataSourceId); + + // Execute migrations + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + workspaceId, + ); + } catch (error) { + console.error('Sync of standard objects failed with:', error); + } + } + + private async syncRelationMetadata( + workspaceId: string, + dataSourceId: string, + ) { + const objectsInDB = await this.objectMetadataRepository.find({ + where: { workspaceId, dataSourceId, isCustom: false }, + relations: ['fields'], + }); + const objectsInDBByName = mapObjectMetadataByUniqueIdentifier(objectsInDB); + const standardRelations = MetadataParser.parseAllRelations( + standardObjectMetadata, + workspaceId, + objectsInDBByName, + ).reduce((result, currentObject) => { + const key = `${currentObject.fromObjectMetadataId}->${currentObject.fromFieldMetadataId}`; + result[key] = currentObject; + return result; + }, {}); + + // TODO: filter out custom relations once isCustom has been added to relationMetadata table + const relationsInDB = await this.relationMetadataRepository.find({ + where: { workspaceId }, + }); + + // We filter out 'id' later because we need it to remove the relation from DB + const relationsInDBWithoutIgnoredProperties = relationsInDB + .map((relation) => + filterIgnoredProperties(relation, ['createdAt', 'updatedAt']), + ) + .reduce((result, currentObject) => { + const key = `${currentObject.fromObjectMetadataId}->${currentObject.fromFieldMetadataId}`; + result[key] = currentObject; + return result; + }, {}); + + // Compare relations + const relationsDiff = diff( + relationsInDBWithoutIgnoredProperties, + standardRelations, + ); + + const relationsToCreate: RelationMetadataEntity[] = []; + const relationsToDelete: RelationMetadataEntity[] = []; + + for (const diff of relationsDiff) { + if (diff.type === 'CREATE') { + relationsToCreate.push(diff.value); + } + if (diff.type === 'REMOVE' && diff.path[diff.path.length - 1] !== 'id') { + relationsToDelete.push(diff.oldValue); + } + } + + try { + // CREATE RELATIONS + await this.relationMetadataRepository.save(relationsToCreate); + // DELETE RELATIONS + if (relationsToDelete.length > 0) { + await this.relationMetadataRepository.delete( + relationsToDelete.map((relation) => relation.id), + ); + } + + await this.generateRelationMigrationsFromSync( + relationsToCreate, + relationsToDelete, + objectsInDB, + ); + } catch (error) { + console.error('Sync of standard relations failed with:', error); + } + } + + private async generateMigrationsFromSync( + objectsToCreate: ObjectMetadataEntity[], + _objectsToDelete: ObjectMetadataEntity[], + _fieldsToCreate: FieldMetadataEntity[], + _fieldsToDelete: FieldMetadataEntity[], + ) { + const migrationsToSave: Partial[] = []; + + if (objectsToCreate.length > 0) { + objectsToCreate.map((object) => { + const migrations = [ + { + name: object.targetTableName, + action: 'create', + } satisfies WorkspaceMigrationTableAction, + ...Object.values(object.fields) + .filter((field) => field.type !== FieldMetadataType.RELATION) + .map( + (field) => + ({ + name: object.targetTableName, + action: 'alter', + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + field, + ), + } satisfies WorkspaceMigrationTableAction), + ), + ]; + + migrationsToSave.push({ + workspaceId: object.workspaceId, + isCustom: false, + migrations, + }); + }); + } + + await this.workspaceMigrationRepository.save(migrationsToSave); + + // TODO: handle delete migrations + } + + private async generateRelationMigrationsFromSync( + relationsToCreate: RelationMetadataEntity[], + _relationsToDelete: RelationMetadataEntity[], + objectsInDB: ObjectMetadataEntity[], + ) { + const relationsMigrationsToSave: Partial[] = []; + + if (relationsToCreate.length > 0) { + relationsToCreate.map((relation) => { + const toObjectMetadata = objectsInDB.find( + (object) => object.id === relation.toObjectMetadataId, + ); + + const fromObjectMetadata = objectsInDB.find( + (object) => object.id === relation.fromObjectMetadataId, + ); + + if (!toObjectMetadata) { + throw new Error( + `ObjectMetadata with id ${relation.toObjectMetadataId} not found`, + ); + } + + if (!fromObjectMetadata) { + throw new Error( + `ObjectMetadata with id ${relation.fromObjectMetadataId} not found`, + ); + } + + const toFieldMetadata = toObjectMetadata.fields.find( + (field) => field.id === relation.toFieldMetadataId, + ); + + if (!toFieldMetadata) { + throw new Error( + `FieldMetadata with id ${relation.toFieldMetadataId} not found`, + ); + } + + const migrations = [ + { + name: toObjectMetadata.targetTableName, + action: 'alter', + columns: [ + { + action: WorkspaceMigrationColumnActionType.RELATION, + columnName: `${camelCase(toFieldMetadata.name)}Id`, + referencedTableName: fromObjectMetadata.targetTableName, + referencedTableColumnName: 'id', + isUnique: + relation.relationType === RelationMetadataType.ONE_TO_ONE, + } satisfies WorkspaceMigrationColumnRelation, + ], + } satisfies WorkspaceMigrationTableAction, + ]; + + relationsMigrationsToSave.push({ + workspaceId: relation.workspaceId, + isCustom: false, + migrations, + }); + }); + } + + await this.workspaceMigrationRepository.save(relationsMigrationsToSave); + + // TODO: handle delete migrations + } +}