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
This commit is contained in:
@ -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,
|
||||
],
|
||||
})
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
};
|
||||
@ -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<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
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();
|
||||
};
|
||||
|
||||
@ -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<TypeOrmModuleOptions> => ({
|
||||
useFactory: coreTypeORMFactory,
|
||||
name: 'core',
|
||||
}),
|
||||
EnvironmentModule,
|
||||
],
|
||||
providers: [TypeORMService],
|
||||
exports: [TypeORMService],
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<string, FieldMetadataInterface[]>();
|
||||
private compositeDefinitions = new Map<
|
||||
string,
|
||||
CompositeFieldSplitterFunction
|
||||
>();
|
||||
|
||||
constructor(
|
||||
private readonly basicColumnActionFactory: BasicColumnActionFactory,
|
||||
@ -83,11 +90,13 @@ export class WorkspaceMigrationFactory {
|
||||
],
|
||||
]);
|
||||
|
||||
this.compositeDefinitions = new Map<string, FieldMetadataInterface[]>([
|
||||
[FieldMetadataType.LINK, linkObjectDefinition.fields],
|
||||
[FieldMetadataType.CURRENCY, currencyObjectDefinition.fields],
|
||||
[FieldMetadataType.FULL_NAME, fullNameObjectDefinition.fields],
|
||||
]);
|
||||
this.compositeDefinitions = new Map<string, CompositeFieldSplitterFunction>(
|
||||
[
|
||||
[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),
|
||||
);
|
||||
|
||||
@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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<FieldMetadataEntity>[] = [
|
||||
{
|
||||
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' },
|
||||
},
|
||||
];
|
||||
@ -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,
|
||||
];
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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],
|
||||
|
||||
@ -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<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
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<ObjectMetadataEntity[]> {
|
||||
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<RelationMetadataEntity>
|
||||
*/
|
||||
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<string, ObjectMetadataEntity> = {};
|
||||
|
||||
const fieldsToCreate: FieldMetadataEntity[] = [];
|
||||
const fieldsToDelete: FieldMetadataEntity[] = [];
|
||||
const fieldsToUpdate: Record<string, FieldMetadataEntity> = {};
|
||||
|
||||
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,
|
||||
|
||||
@ -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,
|
||||
);
|
||||
@ -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 {}
|
||||
@ -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,
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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()
|
||||
@ -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;
|
||||
}
|
||||
@ -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({
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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,
|
||||
];
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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',
|
||||
@ -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[];
|
||||
}
|
||||
@ -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<string, ObjectMetadataEntity>,
|
||||
) {
|
||||
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<string, ObjectMetadataEntity>,
|
||||
) {
|
||||
return metadata.flatMap((_metadata) =>
|
||||
MetadataParser.parseRelationMetadata(
|
||||
_metadata,
|
||||
workspaceId,
|
||||
objectMetadataFromDB,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
};
|
||||
@ -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 {}
|
||||
@ -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<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(RelationMetadataEntity, 'metadata')
|
||||
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
|
||||
@InjectRepository(WorkspaceMigrationEntity, 'metadata')
|
||||
private readonly workspaceMigrationRepository: Repository<WorkspaceMigrationEntity>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* 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<string, ObjectMetadataEntity> = {};
|
||||
|
||||
const fieldsToCreate: FieldMetadataEntity[] = [];
|
||||
const fieldsToDelete: FieldMetadataEntity[] = [];
|
||||
const fieldsToUpdate: Record<string, FieldMetadataEntity> = {};
|
||||
|
||||
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<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
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<WorkspaceMigrationEntity>[] = [];
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user