Convert metadata tables to camelCase (#2400)
* Convert metadata tables to camelCase * datasourcemetadataid to datasourceid * refactor metadata folders * fix command * move commands out of metadata * fix seed * rename objectId and fieldId in objectMetadataId and fieldMetadataId in FE * fix field-metadata * Fix * Fix * remove logs --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,69 +0,0 @@
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
|
||||
import { Command, CommandRunner } from 'nest-commander';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { seedCompanies } from 'src/database/typeorm-seeds/tenant/companies';
|
||||
import { seedViewFields } from 'src/database/typeorm-seeds/tenant/view-fields';
|
||||
import { seedViews } from 'src/database/typeorm-seeds/tenant/views';
|
||||
import { seedFieldMetadata } from 'src/database/typeorm-seeds/metadata/field-metadata';
|
||||
import { seedObjectMetadata } from 'src/database/typeorm-seeds/metadata/object-metadata';
|
||||
|
||||
// TODO: implement dry-run
|
||||
@Command({
|
||||
name: 'tenant:seed',
|
||||
description:
|
||||
'Seed tenant with initial data. This command is intended for development only.',
|
||||
})
|
||||
export class DataSeedTenantCommand extends CommandRunner {
|
||||
workspaceId = 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419';
|
||||
|
||||
constructor(
|
||||
@InjectDataSource('metadata')
|
||||
private readonly metadataDataSource: DataSource,
|
||||
private readonly dataSourceMetadataService: DataSourceMetadataService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
private readonly migrationRunnerService: MigrationRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
this.workspaceId,
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.dataSourceService.connectToWorkspaceDataSource(
|
||||
this.workspaceId,
|
||||
);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Could not connect to workspace data source');
|
||||
}
|
||||
|
||||
await seedObjectMetadata(this.metadataDataSource, 'metadata');
|
||||
await seedFieldMetadata(this.metadataDataSource, 'metadata');
|
||||
|
||||
await this.tenantMigrationService.insertStandardMigrations(
|
||||
this.workspaceId,
|
||||
);
|
||||
await this.migrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
this.workspaceId,
|
||||
);
|
||||
|
||||
await seedCompanies(workspaceDataSource, dataSourceMetadata.schema);
|
||||
await seedViewFields(workspaceDataSource, dataSourceMetadata.schema);
|
||||
await seedViews(workspaceDataSource, dataSourceMetadata.schema);
|
||||
|
||||
await this.dataSourceService.disconnectFromWorkspaceDataSource(
|
||||
this.workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
import { FieldMetadataModule } from 'src/metadata/field-metadata/field-metadata.module';
|
||||
import { TenantInitialisationModule } from 'src/metadata/tenant-initialisation/tenant-initialisation.module';
|
||||
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
|
||||
import { SyncTenantMetadataCommand } from './sync-tenant-metadata.command';
|
||||
import { RunTenantMigrationsCommand } from './run-tenant-migrations.command';
|
||||
import { DataSeedTenantCommand } from './data-seed-tenant.command';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TenantMigrationModule,
|
||||
MigrationRunnerModule,
|
||||
ObjectMetadataModule,
|
||||
FieldMetadataModule,
|
||||
DataSourceMetadataModule,
|
||||
TenantInitialisationModule,
|
||||
DataSourceModule,
|
||||
],
|
||||
providers: [
|
||||
RunTenantMigrationsCommand,
|
||||
SyncTenantMetadataCommand,
|
||||
DataSeedTenantCommand,
|
||||
],
|
||||
})
|
||||
export class MetadataCommandModule {}
|
||||
@ -1,45 +0,0 @@
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
|
||||
// TODO: implement dry-run
|
||||
interface RunTenantMigrationsOptions {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
@Command({
|
||||
name: 'tenant:migrate',
|
||||
description: 'Run tenant migrations',
|
||||
})
|
||||
export class RunTenantMigrationsCommand extends CommandRunner {
|
||||
constructor(
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
private readonly migrationRunnerService: MigrationRunnerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async run(
|
||||
_passedParam: string[],
|
||||
options: RunTenantMigrationsOptions,
|
||||
): Promise<void> {
|
||||
// TODO: run in a dedicated job + run queries in a transaction.
|
||||
await this.tenantMigrationService.insertStandardMigrations(
|
||||
options.workspaceId,
|
||||
);
|
||||
await this.migrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
options.workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: workspaceId should be optional and we should run migrations for all workspaces
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description: 'workspace id',
|
||||
required: true,
|
||||
})
|
||||
parseWorkspaceId(value: string): string {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||
|
||||
// TODO: implement dry-run
|
||||
interface RunTenantMigrationsOptions {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
@Command({
|
||||
name: 'tenant:sync-metadata',
|
||||
description: 'Sync metadata',
|
||||
})
|
||||
export class SyncTenantMetadataCommand extends CommandRunner {
|
||||
constructor(
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly dataSourceMetadataService: DataSourceMetadataService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async run(
|
||||
_passedParam: string[],
|
||||
options: RunTenantMigrationsOptions,
|
||||
): Promise<void> {
|
||||
// TODO: run in a dedicated job + run queries in a transaction.
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceMetadataService.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.objectMetadataService.deleteMany({
|
||||
workspaceId: { eq: options.workspaceId },
|
||||
});
|
||||
|
||||
await this.objectMetadataService.createStandardObjectsAndFieldsMetadata(
|
||||
dataSourceMetadata.id,
|
||||
options.workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description: 'workspace id',
|
||||
required: true,
|
||||
})
|
||||
parseWorkspaceId(value: string): string {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
DataSourceOptions,
|
||||
} from 'typeorm';
|
||||
|
||||
type DataSourceType = DataSourceOptions['type'];
|
||||
|
||||
@Entity('data_source_metadata')
|
||||
export class DataSourceMetadata {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
url: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
schema: string;
|
||||
|
||||
@Column({ type: 'enum', enum: ['postgres'], default: 'postgres' })
|
||||
type: DataSourceType;
|
||||
|
||||
@Column({ nullable: true, name: 'label' })
|
||||
label: string;
|
||||
|
||||
@Column({ default: false, name: 'is_remote' })
|
||||
isRemote: boolean;
|
||||
|
||||
@Column({ nullable: false, name: 'workspace_id' })
|
||||
workspaceId: string;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSourceEntity } from 'src/database/typeorm/metadata/entities/data-source.entity';
|
||||
|
||||
import { DataSourceMetadataService } from './data-source-metadata.service';
|
||||
import { DataSourceMetadata } from './data-source-metadata.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([DataSourceMetadata], 'metadata')],
|
||||
imports: [TypeOrmModule.forFeature([DataSourceEntity], 'metadata')],
|
||||
providers: [DataSourceMetadataService],
|
||||
exports: [DataSourceMetadataService],
|
||||
})
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSourceMetadataService } from './data-source-metadata.service';
|
||||
import { DataSourceMetadata } from './data-source-metadata.entity';
|
||||
|
||||
describe('DataSourceMetadataService', () => {
|
||||
let service: DataSourceMetadataService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
DataSourceMetadataService,
|
||||
{
|
||||
provide: getRepositoryToken(DataSourceMetadata, 'metadata'),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DataSourceMetadataService>(DataSourceMetadataService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -3,13 +3,13 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DataSourceMetadata } from './data-source-metadata.entity';
|
||||
import { DataSourceEntity } from 'src/database/typeorm/metadata/entities/data-source.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DataSourceMetadataService {
|
||||
constructor(
|
||||
@InjectRepository(DataSourceMetadata, 'metadata')
|
||||
private readonly dataSourceMetadataRepository: Repository<DataSourceMetadata>,
|
||||
@InjectRepository(DataSourceEntity, 'metadata')
|
||||
private readonly dataSourceMetadataRepository: Repository<DataSourceEntity>,
|
||||
) {}
|
||||
|
||||
async createDataSourceMetadata(workspaceId: string, workspaceSchema: string) {
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
|
||||
|
||||
import { DataSourceService } from './data-source.service';
|
||||
|
||||
@Module({
|
||||
imports: [DataSourceMetadataModule],
|
||||
providers: [DataSourceService],
|
||||
exports: [DataSourceService],
|
||||
})
|
||||
export class DataSourceModule {}
|
||||
@ -1,34 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||
|
||||
import { DataSourceService } from './data-source.service';
|
||||
|
||||
describe('DataSourceService', () => {
|
||||
let service: DataSourceService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
DataSourceService,
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {
|
||||
getPGDatabaseUrl: () => '',
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DataSourceMetadataService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DataSourceService>(DataSourceService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,153 +0,0 @@
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||
import { TenantMigration } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
|
||||
import { uuidToBase36 } from './data-source.util';
|
||||
|
||||
@Injectable()
|
||||
export class DataSourceService implements OnModuleInit, OnModuleDestroy {
|
||||
private mainDataSource: DataSource;
|
||||
private dataSources: Map<string, DataSource> = new Map();
|
||||
|
||||
constructor(
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly dataSourceMetadataService: DataSourceMetadataService,
|
||||
) {
|
||||
this.mainDataSource = new DataSource({
|
||||
url: environmentService.getPGDatabaseUrl(),
|
||||
type: 'postgres',
|
||||
logging: false,
|
||||
schema: 'public',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new schema for a given workspaceId
|
||||
* @param workspaceId
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async createWorkspaceSchema(workspaceId: string): Promise<string> {
|
||||
const schemaName = this.getSchemaName(workspaceId);
|
||||
|
||||
const queryRunner = this.mainDataSource.createQueryRunner();
|
||||
const schemaAlreadyExists = await queryRunner.hasSchema(schemaName);
|
||||
|
||||
if (schemaAlreadyExists) {
|
||||
throw new Error(`Schema ${schemaName} already exists`);
|
||||
}
|
||||
|
||||
await queryRunner.createSchema(schemaName, true);
|
||||
|
||||
await queryRunner.release();
|
||||
|
||||
return schemaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a workspace data source using the workspace metadata. Returns a cached connection if it exists.
|
||||
* @param workspaceId
|
||||
* @returns Promise<DataSource | undefined>
|
||||
*/
|
||||
public async connectToWorkspaceDataSource(
|
||||
workspaceId: string,
|
||||
): Promise<DataSource | undefined> {
|
||||
if (this.dataSources.has(workspaceId)) {
|
||||
const cachedDataSource = this.dataSources.get(workspaceId);
|
||||
return cachedDataSource;
|
||||
}
|
||||
|
||||
// We only want the first one for now, we will handle multiple data sources later with remote datasources.
|
||||
// However, we will need to differentiate the data sources because we won't run migrations on remote data sources for example.
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
const schema = dataSourceMetadata.schema;
|
||||
|
||||
// Probably not needed as we will ask for the schema name OR store public by default if it's remote
|
||||
if (!schema && !dataSourceMetadata.isRemote) {
|
||||
throw Error(
|
||||
"No schema found for this non-remote data source, we don't want to fallback to public for workspace data sources.",
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceDataSource = new DataSource({
|
||||
// TODO: We should use later dataSourceMetadata.type and use a switch case condition to create the right data source
|
||||
url: dataSourceMetadata.url ?? this.environmentService.getPGDatabaseUrl(),
|
||||
type: 'postgres',
|
||||
logging: ['query'],
|
||||
schema,
|
||||
entities: {
|
||||
TenantMigration,
|
||||
},
|
||||
});
|
||||
|
||||
await workspaceDataSource.initialize();
|
||||
|
||||
this.dataSources.set(workspaceId, workspaceDataSource);
|
||||
|
||||
return workspaceDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from a workspace data source.
|
||||
* @param workspaceId
|
||||
* @returns Promise<void>
|
||||
*
|
||||
*/
|
||||
public async disconnectFromWorkspaceDataSource(workspaceId: string) {
|
||||
if (!this.dataSources.has(workspaceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSource = this.dataSources.get(workspaceId);
|
||||
|
||||
await dataSource?.destroy();
|
||||
|
||||
this.dataSources.delete(workspaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns the schema name for a given workspaceId
|
||||
* @param workspaceId
|
||||
* @returns string
|
||||
*/
|
||||
public getSchemaName(workspaceId: string): string {
|
||||
return `workspace_${uuidToBase36(workspaceId)}`;
|
||||
}
|
||||
|
||||
public async deleteWorkspaceSchema(workspaceId: string) {
|
||||
const schemaName = this.getSchemaName(workspaceId);
|
||||
const queryRunner = this.mainDataSource.createQueryRunner();
|
||||
const schemaAlreadyExists = await queryRunner.hasSchema(schemaName);
|
||||
|
||||
if (!schemaAlreadyExists) {
|
||||
await queryRunner.release();
|
||||
return;
|
||||
}
|
||||
|
||||
await queryRunner.dropSchema(schemaName, true, true);
|
||||
|
||||
await queryRunner.release();
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// Init main data source "default" schema
|
||||
await this.mainDataSource.initialize();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
// Destroy main data source "default" schema
|
||||
await this.mainDataSource.destroy();
|
||||
|
||||
// Destroy all workspace data sources
|
||||
for (const [, dataSource] of this.dataSources) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Converts a UUID to a base 36 string.
|
||||
* This is used to generate the schema name since hyphens from workspace uuid are not allowed in postgres schema names.
|
||||
*
|
||||
* @param uuid
|
||||
* @returns
|
||||
*/
|
||||
export function uuidToBase36(uuid: string): string {
|
||||
let devId = false;
|
||||
|
||||
if (uuid.startsWith('twenty-')) {
|
||||
devId = true;
|
||||
// Clean dev uuids (twenty-)
|
||||
uuid = uuid.replace('twenty-', '');
|
||||
}
|
||||
const hexString = uuid.replace(/-/g, '');
|
||||
const base10Number = BigInt('0x' + hexString);
|
||||
const base36String = base10Number.toString(36);
|
||||
|
||||
return `${devId ? 'twenty_' : ''}${base36String}`;
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { BeforeCreateOne } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
@ -8,9 +9,13 @@ import {
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataTargetColumnMap } from 'src/tenant/schema-builder/interfaces/field-metadata-target-column-map.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
import { BeforeCreateOneField } from 'src/metadata/field-metadata/hooks/before-create-one-field.hook';
|
||||
|
||||
@InputType()
|
||||
@BeforeCreateOne(BeforeCreateOneField)
|
||||
export class CreateFieldInput {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ -29,7 +34,7 @@ export class CreateFieldInput {
|
||||
|
||||
@IsUUID()
|
||||
@Field()
|
||||
objectId: string;
|
||||
objectMetadataId: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ -40,4 +45,10 @@ export class CreateFieldInput {
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@HideField()
|
||||
targetColumnMap: FieldMetadataTargetColumnMap;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
import {
|
||||
Field,
|
||||
HideField,
|
||||
ID,
|
||||
ObjectType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
Relation,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { FieldMetadataType } from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
import { RelationMetadataDTO } from 'src/metadata/relation-metadata/dtos/relation-metadata.dto';
|
||||
|
||||
registerEnumType(FieldMetadataType, {
|
||||
name: 'FieldMetadataType',
|
||||
description: 'Type of the field',
|
||||
});
|
||||
|
||||
@ObjectType('field')
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableFilter: true,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@Relation('toRelationMetadata', () => RelationMetadataDTO, {
|
||||
nullable: true,
|
||||
})
|
||||
@Relation('fromRelationMetadata', () => RelationMetadataDTO, {
|
||||
nullable: true,
|
||||
})
|
||||
export class FieldMetadataDTO {
|
||||
@IDField(() => ID)
|
||||
id: string;
|
||||
|
||||
@Field(() => FieldMetadataType)
|
||||
type: FieldMetadataType;
|
||||
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@Field()
|
||||
label: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
icon: string;
|
||||
|
||||
@Field({ nullable: true, deprecationReason: 'Use label name instead' })
|
||||
placeholder?: string;
|
||||
|
||||
@Field()
|
||||
isCustom: boolean;
|
||||
|
||||
@Field()
|
||||
isActive: boolean;
|
||||
|
||||
@Field()
|
||||
isNullable: boolean;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
|
||||
import { FieldMetadata } from './field-metadata.entity';
|
||||
|
||||
import { FieldMetadataService } from './services/field-metadata.service';
|
||||
import { CreateFieldInput } from './dtos/create-field.input';
|
||||
import { UpdateFieldInput } from './dtos/update-field.input';
|
||||
|
||||
export const fieldMetadataAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: FieldMetadata,
|
||||
DTOClass: FieldMetadata,
|
||||
CreateDTOClass: CreateFieldInput,
|
||||
UpdateDTOClass: UpdateFieldInput,
|
||||
ServiceClass: FieldMetadataService,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
@ -1,141 +0,0 @@
|
||||
import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import {
|
||||
Authorize,
|
||||
BeforeCreateOne,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
Relation,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/tenant/schema-builder/interfaces/field-metadata.interface';
|
||||
|
||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { RelationMetadata } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||
|
||||
import { BeforeCreateOneField } from './hooks/before-create-one-field.hook';
|
||||
import { FieldMetadataTargetColumnMap } from './interfaces/field-metadata-target-column-map.interface';
|
||||
|
||||
export enum FieldMetadataType {
|
||||
UUID = 'uuid',
|
||||
TEXT = 'TEXT',
|
||||
PHONE = 'PHONE',
|
||||
EMAIL = 'EMAIL',
|
||||
DATE = 'DATE',
|
||||
BOOLEAN = 'BOOLEAN',
|
||||
NUMBER = 'NUMBER',
|
||||
ENUM = 'ENUM',
|
||||
URL = 'URL',
|
||||
MONEY = 'MONEY',
|
||||
RELATION = 'RELATION',
|
||||
}
|
||||
|
||||
registerEnumType(FieldMetadataType, {
|
||||
name: 'FieldMetadataType',
|
||||
description: 'Type of the field',
|
||||
});
|
||||
|
||||
@Entity('field_metadata')
|
||||
@ObjectType('field')
|
||||
@BeforeCreateOne(BeforeCreateOneField)
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableFilter: true,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@Unique('IndexOnNameObjectIdAndWorkspaceIdUnique', [
|
||||
'name',
|
||||
'objectId',
|
||||
'workspaceId',
|
||||
])
|
||||
@Relation('toRelationMetadata', () => RelationMetadata, { nullable: true })
|
||||
@Relation('fromRelationMetadata', () => RelationMetadata, { nullable: true })
|
||||
export class FieldMetadata implements FieldMetadataInterface {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false, name: 'object_id' })
|
||||
objectId: string;
|
||||
|
||||
@Field(() => FieldMetadataType)
|
||||
@Column({ nullable: false })
|
||||
type: FieldMetadataType;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false })
|
||||
label: string;
|
||||
|
||||
@Column({ nullable: false, name: 'target_column_map', type: 'jsonb' })
|
||||
targetColumnMap: FieldMetadataTargetColumnMap;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true, name: 'description', type: 'text' })
|
||||
description: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true, name: 'icon' })
|
||||
icon: string;
|
||||
|
||||
@Field({ nullable: true, deprecationReason: 'Use label name instead' })
|
||||
placeholder: string;
|
||||
|
||||
@Column('text', { nullable: true, array: true })
|
||||
enums: string[];
|
||||
|
||||
@Field()
|
||||
@Column({ default: false, name: 'is_custom' })
|
||||
isCustom: boolean;
|
||||
|
||||
@Field()
|
||||
@Column({ default: false, name: 'is_active' })
|
||||
isActive: boolean;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: true, default: true, name: 'is_nullable' })
|
||||
isNullable: boolean;
|
||||
|
||||
@Column({ nullable: false, name: 'workspace_id' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => ObjectMetadata, (object) => object.fields, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'object_id' })
|
||||
object: ObjectMetadata;
|
||||
|
||||
@OneToOne(() => RelationMetadata, (relation) => relation.fromFieldMetadata)
|
||||
fromRelationMetadata: RelationMetadata;
|
||||
|
||||
@OneToOne(() => RelationMetadata, (relation) => relation.toFieldMetadata)
|
||||
toRelationMetadata: RelationMetadata;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -1,28 +1,56 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
NestjsQueryGraphQLModule,
|
||||
PagingStrategies,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
|
||||
import { TenantMigrationRunnerModule } from 'src/tenant-migration-runner/tenant-migration-runner.module';
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
import { FieldMetadataEntity } from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
|
||||
import { FieldMetadata } from './field-metadata.entity';
|
||||
import { fieldMetadataAutoResolverOpts } from './field-metadata.auto-resolver-opts';
|
||||
import { FieldMetadataService } from './field-metadata.service';
|
||||
|
||||
import { FieldMetadataService } from './services/field-metadata.service';
|
||||
import { CreateFieldInput } from './dtos/create-field.input';
|
||||
import { FieldMetadataDTO } from './dtos/field-metadata.dto';
|
||||
import { UpdateFieldInput } from './dtos/update-field.input';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([FieldMetadata], 'metadata'),
|
||||
NestjsQueryTypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
|
||||
TenantMigrationModule,
|
||||
MigrationRunnerModule,
|
||||
TenantMigrationRunnerModule,
|
||||
ObjectMetadataModule,
|
||||
],
|
||||
services: [FieldMetadataService],
|
||||
resolvers: fieldMetadataAutoResolverOpts,
|
||||
resolvers: [
|
||||
{
|
||||
EntityClass: FieldMetadataEntity,
|
||||
DTOClass: FieldMetadataDTO,
|
||||
CreateDTOClass: CreateFieldInput,
|
||||
UpdateDTOClass: UpdateFieldInput,
|
||||
ServiceClass: FieldMetadataService,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
providers: [FieldMetadataService],
|
||||
|
||||
@ -10,37 +10,38 @@ import { Repository } from 'typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { DeleteOneOptions } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
convertFieldMetadataToColumnActions,
|
||||
generateTargetColumnMap,
|
||||
} from 'src/metadata/field-metadata/utils/field-metadata.util';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { TenantMigrationRunnerService } from 'src/tenant-migration-runner/tenant-migration-runner.service';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
import { FieldMetadataEntity } from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadata.dto';
|
||||
import { CreateFieldInput } from 'src/metadata/field-metadata/dtos/create-field.input';
|
||||
import { TenantMigrationTableAction } from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
@Injectable()
|
||||
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
||||
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntity> {
|
||||
constructor(
|
||||
@InjectRepository(FieldMetadata, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadata>,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
private readonly migrationRunnerService: MigrationRunnerService,
|
||||
private readonly migrationRunnerService: TenantMigrationRunnerService,
|
||||
) {
|
||||
super(fieldMetadataRepository);
|
||||
}
|
||||
|
||||
override async deleteOne(
|
||||
id: string,
|
||||
opts?: DeleteOneOptions<FieldMetadata> | undefined,
|
||||
): Promise<FieldMetadata> {
|
||||
opts?: DeleteOneOptions<FieldMetadataDTO> | undefined,
|
||||
): Promise<FieldMetadataEntity> {
|
||||
const fieldMetadata = await this.fieldMetadataRepository.findOne({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new NotFoundException('Field does not exist');
|
||||
}
|
||||
@ -58,10 +59,12 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
||||
return super.deleteOne(id, opts);
|
||||
}
|
||||
|
||||
override async createOne(record: FieldMetadata): Promise<FieldMetadata> {
|
||||
override async createOne(
|
||||
record: CreateFieldInput,
|
||||
): Promise<FieldMetadataEntity> {
|
||||
const objectMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(
|
||||
record.objectId,
|
||||
record.objectMetadataId,
|
||||
record.workspaceId,
|
||||
);
|
||||
|
||||
@ -72,7 +75,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
||||
const fieldAlreadyExists = await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
name: record.name,
|
||||
objectId: record.objectId,
|
||||
objectMetadataId: record.objectMetadataId,
|
||||
workspaceId: record.workspaceId,
|
||||
},
|
||||
});
|
||||
@ -83,11 +86,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
||||
|
||||
const createdFieldMetadata = await super.createOne({
|
||||
...record,
|
||||
targetColumnMap: generateTargetColumnMap(
|
||||
record.type,
|
||||
record.isCustom,
|
||||
record.name,
|
||||
),
|
||||
targetColumnMap: generateTargetColumnMap(record.type, true, record.name),
|
||||
isActive: true,
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
await this.tenantMigrationService.createCustomMigration(
|
||||
@ -5,10 +5,10 @@ import {
|
||||
CreateOneInputType,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { CreateFieldInput } from 'src/metadata/field-metadata/dtos/create-field.input';
|
||||
|
||||
@Injectable()
|
||||
export class BeforeCreateOneField<T extends FieldMetadata>
|
||||
export class BeforeCreateOneField<T extends CreateFieldInput>
|
||||
implements BeforeCreateOneHook<T, any>
|
||||
{
|
||||
async run(
|
||||
@ -22,8 +22,6 @@ export class BeforeCreateOneField<T extends FieldMetadata>
|
||||
}
|
||||
|
||||
instance.input.workspaceId = workspaceId;
|
||||
instance.input.isActive = true;
|
||||
instance.input.isCustom = true;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
|
||||
export interface FieldMetadataTargetColumnMapValue {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface FieldMetadataTargetColumnMapUrl {
|
||||
text: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface FieldMetadataTargetColumnMapMoney {
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
type AllFieldMetadataTypes = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type FieldMetadataTypeMapping = {
|
||||
[FieldMetadataType.URL]: FieldMetadataTargetColumnMapUrl;
|
||||
[FieldMetadataType.MONEY]: FieldMetadataTargetColumnMapMoney;
|
||||
};
|
||||
|
||||
type TypeByFieldMetadata<T extends FieldMetadataType | 'default'> =
|
||||
T extends keyof FieldMetadataTypeMapping
|
||||
? FieldMetadataTypeMapping[T]
|
||||
: T extends 'default'
|
||||
? AllFieldMetadataTypes
|
||||
: FieldMetadataTargetColumnMapValue;
|
||||
|
||||
export type FieldMetadataTargetColumnMap<
|
||||
T extends FieldMetadataType | 'default' = 'default',
|
||||
> = TypeByFieldMetadata<T>;
|
||||
@ -1,43 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||
|
||||
import { FieldMetadataService } from './field-metadata.service';
|
||||
|
||||
describe('FieldMetadataService', () => {
|
||||
let service: FieldMetadataService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
FieldMetadataService,
|
||||
{
|
||||
provide: getRepositoryToken(FieldMetadata, 'metadata'),
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: ObjectMetadataService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: TenantMigrationService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: MigrationRunnerService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<FieldMetadataService>(FieldMetadataService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataType } from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
|
||||
import {
|
||||
generateTargetColumnMap,
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { FieldMetadataTargetColumnMap } from 'src/metadata/field-metadata/interfaces/field-metadata-target-column-map.interface';
|
||||
import { FieldMetadataTargetColumnMap } from 'src/tenant/schema-builder/interfaces/field-metadata-target-column-map.interface';
|
||||
|
||||
import {
|
||||
FieldMetadata,
|
||||
FieldMetadataEntity,
|
||||
FieldMetadataType,
|
||||
} from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
} from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
import {
|
||||
TenantMigrationColumnAction,
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
/**
|
||||
* Generate a target column map for a given type, this is used to map the field to the correct column(s) in the database.
|
||||
@ -49,7 +49,7 @@ export function generateTargetColumnMap(
|
||||
}
|
||||
|
||||
export function convertFieldMetadataToColumnActions(
|
||||
fieldMetadata: FieldMetadata,
|
||||
fieldMetadata: FieldMetadataEntity,
|
||||
): TenantMigrationColumnAction[] {
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.TEXT:
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||
import { config } from 'dotenv';
|
||||
|
||||
config();
|
||||
|
||||
const configService = new ConfigService();
|
||||
|
||||
export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = {
|
||||
url: configService.get<string>('PG_DATABASE_URL'),
|
||||
type: 'postgres',
|
||||
logging: ['error'],
|
||||
schema: 'metadata',
|
||||
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||
synchronize: false,
|
||||
migrationsRun: false,
|
||||
migrationsTableName: '_typeorm_migrations',
|
||||
migrations: [__dirname + '/migrations/*{.ts,.js}'],
|
||||
};
|
||||
|
||||
export const connectionSource = new DataSource(
|
||||
typeORMMetadataModuleOptions as DataSourceOptions,
|
||||
);
|
||||
@ -1,32 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
|
||||
import { YogaDriverConfig, YogaDriver } from '@graphql-yoga/nestjs';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
|
||||
import { TenantMigrationRunnerModule } from 'src/tenant-migration-runner/tenant-migration-runner.module';
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
|
||||
import { typeORMMetadataModuleOptions } from './metadata.datasource';
|
||||
|
||||
import { DataSourceModule } from './data-source/data-source.module';
|
||||
import { DataSourceMetadataModule } from './data-source-metadata/data-source-metadata.module';
|
||||
import { FieldMetadataModule } from './field-metadata/field-metadata.module';
|
||||
import { ObjectMetadataModule } from './object-metadata/object-metadata.module';
|
||||
import { RelationMetadataModule } from './relation-metadata/relation-metadata.module';
|
||||
|
||||
const typeORMFactory = async (): Promise<TypeOrmModuleOptions> => ({
|
||||
...typeORMMetadataModuleOptions,
|
||||
name: 'metadata',
|
||||
});
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
useFactory: typeORMFactory,
|
||||
name: 'metadata',
|
||||
}),
|
||||
GraphQLModule.forRoot<YogaDriverConfig>({
|
||||
context: ({ req }) => ({ req }),
|
||||
driver: YogaDriver,
|
||||
@ -36,11 +22,10 @@ const typeORMFactory = async (): Promise<TypeOrmModuleOptions> => ({
|
||||
plugins: [],
|
||||
path: '/metadata',
|
||||
}),
|
||||
DataSourceModule,
|
||||
DataSourceMetadataModule,
|
||||
FieldMetadataModule,
|
||||
ObjectMetadataModule,
|
||||
MigrationRunnerModule,
|
||||
TenantMigrationRunnerModule,
|
||||
TenantMigrationModule,
|
||||
RelationMetadataModule,
|
||||
],
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { TableColumnOptions } from 'typeorm';
|
||||
|
||||
export const customTableDefaultColumns: TableColumnOptions[] = [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
isPrimary: true,
|
||||
default: 'public.uuid_generate_v4()',
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: 'timestamp',
|
||||
default: 'now()',
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
type: 'timestamp',
|
||||
default: 'now()',
|
||||
},
|
||||
{
|
||||
name: 'deletedAt',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
},
|
||||
];
|
||||
@ -1,13 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
|
||||
import { MigrationRunnerService } from './migration-runner.service';
|
||||
|
||||
@Module({
|
||||
imports: [DataSourceModule, TenantMigrationModule],
|
||||
exports: [MigrationRunnerService],
|
||||
providers: [MigrationRunnerService],
|
||||
})
|
||||
export class MigrationRunnerModule {}
|
||||
@ -1,32 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
|
||||
import { MigrationRunnerService } from './migration-runner.service';
|
||||
|
||||
describe('MigrationRunnerService', () => {
|
||||
let service: MigrationRunnerService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
MigrationRunnerService,
|
||||
{
|
||||
provide: DataSourceService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: TenantMigrationService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<MigrationRunnerService>(MigrationRunnerService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,219 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { QueryRunner, Table, TableColumn, TableForeignKey } from 'typeorm';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import {
|
||||
TenantMigrationTableAction,
|
||||
TenantMigrationColumnAction,
|
||||
TenantMigrationColumnCreate,
|
||||
TenantMigrationColumnRelation,
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
|
||||
import { customTableDefaultColumns } from './custom-table-default-column.util';
|
||||
|
||||
@Injectable()
|
||||
export class MigrationRunnerService {
|
||||
constructor(
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Executes pending migrations for a given workspace
|
||||
*
|
||||
* @param workspaceId string
|
||||
* @returns Promise<TenantMigrationTableAction[]>
|
||||
*/
|
||||
public async executeMigrationFromPendingMigrations(
|
||||
workspaceId: string,
|
||||
): Promise<TenantMigrationTableAction[]> {
|
||||
const workspaceDataSource =
|
||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Workspace data source not found');
|
||||
}
|
||||
|
||||
const pendingMigrations =
|
||||
await this.tenantMigrationService.getPendingMigrations(workspaceId);
|
||||
|
||||
if (pendingMigrations.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const flattenedPendingMigrations: TenantMigrationTableAction[] =
|
||||
pendingMigrations.reduce((acc, pendingMigration) => {
|
||||
return [...acc, ...pendingMigration.migrations];
|
||||
}, []);
|
||||
|
||||
const queryRunner = workspaceDataSource?.createQueryRunner();
|
||||
const schemaName = this.dataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
// Loop over each migration and create or update the table
|
||||
// TODO: Should be done in a transaction
|
||||
for (const migration of flattenedPendingMigrations) {
|
||||
await this.handleTableChanges(queryRunner, schemaName, migration);
|
||||
}
|
||||
|
||||
// Update appliedAt date for each migration
|
||||
// TODO: Should be done after the migration is successful
|
||||
for (const pendingMigration of pendingMigrations) {
|
||||
await this.tenantMigrationService.setAppliedAtForMigration(
|
||||
workspaceId,
|
||||
pendingMigration,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.release();
|
||||
|
||||
return flattenedPendingMigrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles table changes for a given migration
|
||||
*
|
||||
* @param queryRunner QueryRunner
|
||||
* @param schemaName string
|
||||
* @param tableMigration TenantMigrationTableChange
|
||||
*/
|
||||
private async handleTableChanges(
|
||||
queryRunner: QueryRunner,
|
||||
schemaName: string,
|
||||
tableMigration: TenantMigrationTableAction,
|
||||
) {
|
||||
switch (tableMigration.action) {
|
||||
case 'create':
|
||||
await this.createTable(queryRunner, schemaName, tableMigration.name);
|
||||
break;
|
||||
case 'alter':
|
||||
await this.handleColumnChanges(
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableMigration.name,
|
||||
tableMigration?.columns,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Migration table action ${tableMigration.action} not supported`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table for a given schema and table name
|
||||
*
|
||||
* @param queryRunner QueryRunner
|
||||
* @param schemaName string
|
||||
* @param tableName string
|
||||
*/
|
||||
private async createTable(
|
||||
queryRunner: QueryRunner,
|
||||
schemaName: string,
|
||||
tableName: string,
|
||||
) {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: tableName,
|
||||
schema: schemaName,
|
||||
columns: customTableDefaultColumns,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles column changes for a given migration
|
||||
*
|
||||
* @param queryRunner QueryRunner
|
||||
* @param schemaName string
|
||||
* @param tableName string
|
||||
* @param columnMigrations TenantMigrationColumnAction[]
|
||||
* @returns
|
||||
*/
|
||||
private async handleColumnChanges(
|
||||
queryRunner: QueryRunner,
|
||||
schemaName: string,
|
||||
tableName: string,
|
||||
columnMigrations?: TenantMigrationColumnAction[],
|
||||
) {
|
||||
if (!columnMigrations || columnMigrations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const columnMigration of columnMigrations) {
|
||||
switch (columnMigration.action) {
|
||||
case TenantMigrationColumnActionType.CREATE:
|
||||
await this.createColumn(
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnMigration,
|
||||
);
|
||||
break;
|
||||
case TenantMigrationColumnActionType.RELATION:
|
||||
await this.createForeignKey(
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnMigration,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Migration column action not supported`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a column for a given schema, table name, and column migration
|
||||
*
|
||||
* @param queryRunner QueryRunner
|
||||
* @param schemaName string
|
||||
* @param tableName string
|
||||
* @param migrationColumn TenantMigrationColumnAction
|
||||
*/
|
||||
private async createColumn(
|
||||
queryRunner: QueryRunner,
|
||||
schemaName: string,
|
||||
tableName: string,
|
||||
migrationColumn: TenantMigrationColumnCreate,
|
||||
) {
|
||||
const hasColumn = await queryRunner.hasColumn(
|
||||
`${schemaName}.${tableName}`,
|
||||
migrationColumn.columnName,
|
||||
);
|
||||
if (hasColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
await queryRunner.addColumn(
|
||||
`${schemaName}.${tableName}`,
|
||||
new TableColumn({
|
||||
name: migrationColumn.columnName,
|
||||
type: migrationColumn.columnType,
|
||||
isNullable: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async createForeignKey(
|
||||
queryRunner: QueryRunner,
|
||||
schemaName: string,
|
||||
tableName: string,
|
||||
migrationColumn: TenantMigrationColumnRelation,
|
||||
) {
|
||||
await queryRunner.createForeignKey(
|
||||
`${schemaName}.${tableName}`,
|
||||
new TableForeignKey({
|
||||
columnNames: [migrationColumn.columnName],
|
||||
referencedColumnNames: [migrationColumn.referencedTableColumnName],
|
||||
referencedTableName: migrationColumn.referencedTableName,
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class InitMetadataTables1695214465080 implements MigrationInterface {
|
||||
name = 'InitMetadataTables1695214465080';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "metadata"."data_source_metadata_type_enum" AS ENUM ('postgres', 'mysql');`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."data_source_metadata" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "url" character varying, "schema" character varying, "type" "metadata"."data_source_metadata_type_enum" NOT NULL DEFAULT 'postgres', "display_name" character varying, "is_remote" boolean NOT NULL DEFAULT false, "workspace_id" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_923752b7e62a300a4969bd0e038" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."field_metadata" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "object_id" uuid NOT NULL, "type" character varying NOT NULL, "display_name" character varying NOT NULL, "target_column_name" character varying NOT NULL, "is_custom" boolean NOT NULL DEFAULT false, "workspace_id" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_c75db587904cad6af109b5c65f1" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."object_metadata" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "data_source_id" character varying NOT NULL, "display_name" character varying NOT NULL, "target_table_name" character varying NOT NULL, "is_custom" boolean NOT NULL DEFAULT false, "workspace_id" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_c8c5f885767b356949c18c201c1" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD CONSTRAINT "FK_38179b299795e48887fc99f937a" FOREIGN KEY ("object_id") REFERENCES "metadata"."object_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP CONSTRAINT "FK_38179b299795e48887fc99f937a"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."object_metadata"`);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."field_metadata"`);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."data_source_metadata"`);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AlterFieldMetadataTable1695717691800
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AlterFieldMetadataTable1695717691800';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "enums" text array`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "is_nullable" boolean DEFAULT true`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "metadata"."data_source_metadata_type_enum" RENAME TO "data_source_metadata_type_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "metadata"."data_source_metadata_type_enum" AS ENUM('postgres')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" TYPE "metadata"."data_source_metadata_type_enum" USING "type"::"text"::"metadata"."data_source_metadata_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" SET DEFAULT 'postgres'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "metadata"."data_source_metadata_type_enum_old"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "metadata"."data_source_metadata_type_enum_old" AS ENUM('postgres', 'mysql')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" TYPE "metadata"."data_source_metadata_type_enum_old" USING "type"::"text"::"metadata"."data_source_metadata_type_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "type" SET DEFAULT 'postgres'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "metadata"."data_source_metadata_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "metadata"."data_source_metadata_type_enum_old" RENAME TO "data_source_metadata_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "is_nullable"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "enums"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddTargetColumnMap1696409050890 implements MigrationInterface {
|
||||
name = 'AddTargetColumnMap1696409050890';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "description" text`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "icon" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "placeholder" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "target_column_map" jsonb`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "is_active" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "display_name_singular" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "display_name_plural" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "description" text`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "icon" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "is_active" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "is_active"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "icon"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "description"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "display_name_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "display_name_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "is_active"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "target_column_map"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "placeholder"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "icon"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "description"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class MetadataNameLabelRefactoring1697126636202
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'MetadataNameLabelRefactoring1697126636202';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" RENAME COLUMN "display_name" TO "label"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."tenant_migrations" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "migrations" jsonb, "applied_at" TIMESTAMP, "created_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_cb644cbc7f5092850f25eecb465" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "display_name"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "display_name_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "display_name_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "display_name"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "target_column_name"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "name_singular" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD CONSTRAINT "UQ_8b063d2a685474dbae56cd685d2" UNIQUE ("name_singular")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "name_plural" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD CONSTRAINT "UQ_a2387e1b21120110b7e3db83da1" UNIQUE ("name_plural")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "label_singular" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "label_plural" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "name_singular" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "name_plural" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "label_singular" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "label_plural" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP CONSTRAINT "FK_38179b299795e48887fc99f937a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ALTER COLUMN "id" SET DEFAULT uuid_generate_v4()`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ALTER COLUMN "target_column_map" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD CONSTRAINT "FK_38179b299795e48887fc99f937a" FOREIGN KEY ("object_id") REFERENCES "metadata"."object_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP CONSTRAINT "FK_38179b299795e48887fc99f937a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ALTER COLUMN "target_column_map" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ALTER COLUMN "id" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ALTER COLUMN "id" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD CONSTRAINT "FK_38179b299795e48887fc99f937a" FOREIGN KEY ("object_id") REFERENCES "metadata"."object_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" ALTER COLUMN "id" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "label_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "label_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "name_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "name_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "label_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "label_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP CONSTRAINT "UQ_a2387e1b21120110b7e3db83da1"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "name_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP CONSTRAINT "UQ_8b063d2a685474dbae56cd685d2"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "name_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "target_column_name" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "display_name" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "display_name_plural" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "display_name_singular" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "display_name" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."tenant_migrations"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."data_source_metadata" RENAME COLUMN "label" TO "display_name"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveFieldMetadataPlaceholder1697471445015
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveFieldMetadataPlaceholder1697471445015';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "placeholder"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "placeholder" character varying`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddSoftDelete1697474804403 implements MigrationInterface {
|
||||
name = 'AddSoftDelete1697474804403';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "deleted_at" TIMESTAMP`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "deleted_at" TIMESTAMP`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "deleted_at"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "deleted_at"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveSingularPluralFromFieldLabelAndName1697534910933
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveSingularPluralFromFieldLabelAndName1697534910933';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "name_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "name_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "label_singular"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "label_plural"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "name" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "label" character varying NOT NULL`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "label"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "name"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "label_plural" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "label_singular" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "name_plural" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "name_singular" character varying NOT NULL`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddNameAndIsCustomToTenantMigration1697622715467
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddNameAndIsCustomToTenantMigration1697622715467';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "applied_at"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "created_at"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "name" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "isCustom" boolean NOT NULL DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "appliedAt" TIMESTAMP`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "workspaceId" character varying NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "createdAt" TIMESTAMP NOT NULL DEFAULT now()`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "createdAt"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "workspaceId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "appliedAt"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "isCustom"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" DROP COLUMN "name"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "created_at" TIMESTAMP NOT NULL DEFAULT now()`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."tenant_migrations" ADD "applied_at" TIMESTAMP`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddUniqueConstraintsOnFieldObjectMetadata1697630766924
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddUniqueConstraintsOnFieldObjectMetadata1697630766924';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP CONSTRAINT "UQ_8b063d2a685474dbae56cd685d2"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP CONSTRAINT "UQ_a2387e1b21120110b7e3db83da1"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD CONSTRAINT "IndexOnNameObjectIdAndWorkspaceIdUnique" UNIQUE ("name", "object_id", "workspace_id")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD CONSTRAINT "IndexOnNamePluralAndWorkspaceIdUnique" UNIQUE ("name_plural", "workspace_id")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD CONSTRAINT "IndexOnNameSingularAndWorkspaceIdUnique" UNIQUE ("name_singular", "workspace_id")`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP CONSTRAINT "IndexOnNameSingularAndWorkspaceIdUnique"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP CONSTRAINT "IndexOnNamePluralAndWorkspaceIdUnique"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP CONSTRAINT "IndexOnNameAndWorkspaceIdUnique"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD CONSTRAINT "UQ_a2387e1b21120110b7e3db83da1" UNIQUE ("name_plural")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD CONSTRAINT "UQ_8b063d2a685474dbae56cd685d2" UNIQUE ("name_singular")`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveMetadataSoftDelete1698328717102
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveMetadataSoftDelete1698328717102';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP CONSTRAINT "FK_38179b299795e48887fc99f937a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" DROP COLUMN "deleted_at"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP COLUMN "deleted_at"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD CONSTRAINT "FK_38179b299795e48887fc99f937a" FOREIGN KEY ("object_id") REFERENCES "metadata"."object_metadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" DROP CONSTRAINT "FK_38179b299795e48887fc99f937a"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD "deleted_at" TIMESTAMP`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."object_metadata" ADD "deleted_at" TIMESTAMP`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."field_metadata" ADD CONSTRAINT "FK_38179b299795e48887fc99f937a" FOREIGN KEY ("object_id") REFERENCES "metadata"."object_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddRelationMetadata1699289664146 implements MigrationInterface {
|
||||
name = 'AddRelationMetadata1699289664146';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "metadata"."relationMetadata" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "relationType" character varying NOT NULL, "fromObjectMetadataId" uuid NOT NULL, "toObjectMetadataId" uuid NOT NULL, "fromFieldMetadataId" uuid NOT NULL, "toFieldMetadataId" uuid NOT NULL, "workspaceId" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "REL_3deb257254145a3bdde9575e7d" UNIQUE ("fromFieldMetadataId"), CONSTRAINT "REL_9dea8f90d04edbbf9c541a95c3" UNIQUE ("toFieldMetadataId"), CONSTRAINT "PK_2724f60cb4f17a89481a7e8d7d3" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d" FOREIGN KEY ("fromObjectMetadataId") REFERENCES "metadata"."object_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824" FOREIGN KEY ("toObjectMetadataId") REFERENCES "metadata"."object_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_3deb257254145a3bdde9575e7d6" FOREIGN KEY ("fromFieldMetadataId") REFERENCES "metadata"."field_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_9dea8f90d04edbbf9c541a95c3b" FOREIGN KEY ("toFieldMetadataId") REFERENCES "metadata"."field_metadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_9dea8f90d04edbbf9c541a95c3b"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_3deb257254145a3bdde9575e7d6"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "metadata"."relationMetadata"`);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,12 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { BeforeCreateOne } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
import { BeforeCreateOneObject } from 'src/metadata/object-metadata/hooks/before-create-one-object.hook';
|
||||
|
||||
@InputType()
|
||||
@BeforeCreateOne(BeforeCreateOneObject)
|
||||
export class CreateObjectInput {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ -33,4 +37,10 @@ export class CreateObjectInput {
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@HideField()
|
||||
dataSourceId: string;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import { ObjectType, ID, Field, HideField } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
CursorConnection,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadata.dto';
|
||||
|
||||
@ObjectType('object')
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableFilter: true,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@CursorConnection('fields', () => FieldMetadataDTO)
|
||||
export class ObjectMetadataDTO {
|
||||
@IDField(() => ID)
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
dataSourceId: string;
|
||||
|
||||
@Field()
|
||||
nameSingular: string;
|
||||
|
||||
@Field()
|
||||
namePlural: string;
|
||||
|
||||
@Field()
|
||||
labelSingular: string;
|
||||
|
||||
@Field()
|
||||
labelPlural: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
icon: string;
|
||||
|
||||
@Field()
|
||||
isCustom: boolean;
|
||||
|
||||
@Field()
|
||||
isActive: boolean;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -6,10 +6,10 @@ import {
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { CreateObjectInput } from 'src/metadata/object-metadata/dtos/create-object.input';
|
||||
|
||||
@Injectable()
|
||||
export class BeforeCreateOneObject<T extends ObjectMetadata>
|
||||
export class BeforeCreateOneObject<T extends CreateObjectInput>
|
||||
implements BeforeCreateOneHook<T, any>
|
||||
{
|
||||
constructor(readonly dataSourceMetadataService: DataSourceMetadataService) {}
|
||||
@ -30,10 +30,7 @@ export class BeforeCreateOneObject<T extends ObjectMetadata>
|
||||
);
|
||||
|
||||
instance.input.dataSourceId = lastDataSourceMetadata.id;
|
||||
instance.input.targetTableName = `_${instance.input.namePlural}`;
|
||||
instance.input.workspaceId = workspaceId;
|
||||
instance.input.isActive = true;
|
||||
instance.input.isCustom = true;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
|
||||
import { ObjectMetadata } from './object-metadata.entity';
|
||||
|
||||
import { ObjectMetadataService } from './services/object-metadata.service';
|
||||
import { CreateObjectInput } from './dtos/create-object.input';
|
||||
import { UpdateObjectInput } from './dtos/update-object.input';
|
||||
|
||||
export const objectMetadataAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: ObjectMetadata,
|
||||
DTOClass: ObjectMetadata,
|
||||
CreateDTOClass: CreateObjectInput,
|
||||
UpdateDTOClass: UpdateObjectInput,
|
||||
ServiceClass: ObjectMetadataService,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
@ -1,112 +0,0 @@
|
||||
import { ObjectType, ID, Field } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import {
|
||||
Authorize,
|
||||
BeforeCreateOne,
|
||||
CursorConnection,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { ObjectMetadataInterface } from 'src/tenant/schema-builder/interfaces/object-metadata.interface';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { RelationMetadata } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||
|
||||
import { BeforeCreateOneObject } from './hooks/before-create-one-object.hook';
|
||||
|
||||
@Entity('object_metadata')
|
||||
@ObjectType('object')
|
||||
@BeforeCreateOne(BeforeCreateOneObject)
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableFilter: true,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@CursorConnection('fields', () => FieldMetadata)
|
||||
@Unique('IndexOnNameSingularAndWorkspaceIdUnique', [
|
||||
'nameSingular',
|
||||
'workspaceId',
|
||||
])
|
||||
@Unique('IndexOnNamePluralAndWorkspaceIdUnique', ['namePlural', 'workspaceId'])
|
||||
export class ObjectMetadata implements ObjectMetadataInterface {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, name: 'data_source_id' })
|
||||
dataSourceId: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, name: 'name_singular' })
|
||||
nameSingular: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, name: 'name_plural' })
|
||||
namePlural: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, name: 'label_singular' })
|
||||
labelSingular: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, name: 'label_plural' })
|
||||
labelPlural: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true, name: 'description', type: 'text' })
|
||||
description: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true, name: 'icon' })
|
||||
icon: string;
|
||||
|
||||
@Column({ nullable: false, name: 'target_table_name' })
|
||||
targetTableName: string;
|
||||
|
||||
@Field()
|
||||
@Column({ default: false, name: 'is_custom' })
|
||||
isCustom: boolean;
|
||||
|
||||
@Field()
|
||||
@Column({ default: false, name: 'is_active' })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ nullable: false, name: 'workspace_id' })
|
||||
workspaceId: string;
|
||||
|
||||
@OneToMany(() => FieldMetadata, (field) => field.object, {
|
||||
cascade: true,
|
||||
})
|
||||
fields: FieldMetadata[];
|
||||
|
||||
@OneToMany(() => RelationMetadata, (relation) => relation.fromObjectMetadata)
|
||||
fromRelations: RelationMetadata[];
|
||||
|
||||
@OneToMany(() => RelationMetadata, (relation) => relation.toObjectMetadata)
|
||||
toRelations: RelationMetadata[];
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -1,28 +1,56 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
NestjsQueryGraphQLModule,
|
||||
PagingStrategies,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
|
||||
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
|
||||
import { TenantMigrationRunnerModule } from 'src/tenant-migration-runner/tenant-migration-runner.module';
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { ObjectMetadataEntity } from 'src/database/typeorm/metadata/entities/object-metadata.entity';
|
||||
|
||||
import { ObjectMetadata } from './object-metadata.entity';
|
||||
import { objectMetadataAutoResolverOpts } from './object-metadata.auto-resolver-opts';
|
||||
import { ObjectMetadataService } from './object-metadata.service';
|
||||
|
||||
import { ObjectMetadataService } from './services/object-metadata.service';
|
||||
import { CreateObjectInput } from './dtos/create-object.input';
|
||||
import { UpdateObjectInput } from './dtos/update-object.input';
|
||||
import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadata], 'metadata'),
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
DataSourceMetadataModule,
|
||||
TenantMigrationModule,
|
||||
MigrationRunnerModule,
|
||||
TenantMigrationRunnerModule,
|
||||
],
|
||||
services: [ObjectMetadataService],
|
||||
resolvers: objectMetadataAutoResolverOpts,
|
||||
resolvers: [
|
||||
{
|
||||
EntityClass: ObjectMetadataEntity,
|
||||
DTOClass: ObjectMetadataDTO,
|
||||
CreateDTOClass: CreateObjectInput,
|
||||
UpdateDTOClass: UpdateObjectInput,
|
||||
ServiceClass: ObjectMetadataService,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
providers: [ObjectMetadataService],
|
||||
|
||||
@ -7,30 +7,27 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Equal, In, Repository } from 'typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { DeleteOneOptions } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { standardObjectsMetadata } from 'src/metadata/standard-objects/standard-object-metadata';
|
||||
import { TenantMigrationRunnerService } from 'src/tenant-migration-runner/tenant-migration-runner.service';
|
||||
import { ObjectMetadataEntity } from 'src/database/typeorm/metadata/entities/object-metadata.entity';
|
||||
import { TenantMigrationTableAction } from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
import { CreateObjectInput } from './dtos/create-object.input';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadata> {
|
||||
export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEntity> {
|
||||
constructor(
|
||||
@InjectRepository(ObjectMetadata, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadata>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
private readonly migrationRunnerService: MigrationRunnerService,
|
||||
private readonly migrationRunnerService: TenantMigrationRunnerService,
|
||||
) {
|
||||
super(objectMetadataRepository);
|
||||
}
|
||||
|
||||
override async deleteOne(
|
||||
id: string,
|
||||
opts?: DeleteOneOptions<ObjectMetadata> | undefined,
|
||||
): Promise<ObjectMetadata> {
|
||||
override async deleteOne(id: string): Promise<ObjectMetadataEntity> {
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
where: { id },
|
||||
});
|
||||
@ -47,11 +44,18 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadata> {
|
||||
throw new BadRequestException("Active objects can't be deleted");
|
||||
}
|
||||
|
||||
return super.deleteOne(id, opts);
|
||||
return super.deleteOne(id);
|
||||
}
|
||||
|
||||
override async createOne(record: ObjectMetadata): Promise<ObjectMetadata> {
|
||||
const createdObjectMetadata = await super.createOne(record);
|
||||
override async createOne(
|
||||
record: CreateObjectInput,
|
||||
): Promise<ObjectMetadataEntity> {
|
||||
const createdObjectMetadata = await super.createOne({
|
||||
...record,
|
||||
targetTableName: `_${record.nameSingular}`,
|
||||
isActive: true,
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
await this.tenantMigrationService.createCustomMigration(
|
||||
createdObjectMetadata.workspaceId,
|
||||
@ -103,34 +107,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadata> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Create all standard objects and fields metadata for a given workspace
|
||||
*
|
||||
* @param dataSourceMetadataId
|
||||
* @param workspaceId
|
||||
*/
|
||||
public async createStandardObjectsAndFieldsMetadata(
|
||||
dataSourceMetadataId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
await this.objectMetadataRepository.save(
|
||||
Object.values(standardObjectsMetadata).map((objectMetadata) => ({
|
||||
...objectMetadata,
|
||||
dataSourceId: dataSourceMetadataId,
|
||||
workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
fields: objectMetadata.fields.map((field) => ({
|
||||
...field,
|
||||
workspaceId,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
})),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
public async deleteObjectsMetadata(workspaceId: string) {
|
||||
await this.objectMetadataRepository.delete({ workspaceId });
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
|
||||
import { ObjectMetadataService } from './object-metadata.service';
|
||||
|
||||
describe('ObjectMetadataService', () => {
|
||||
let service: ObjectMetadataService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
ObjectMetadataService,
|
||||
{
|
||||
provide: getRepositoryToken(ObjectMetadata, 'metadata'),
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: TenantMigrationService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: MigrationRunnerService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ObjectMetadataService>(ObjectMetadataService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { BeforeCreateOne } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
@ -9,9 +9,10 @@ import {
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { RelationType } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||
import { BeforeCreateOneRelation } from 'src/metadata/relation-metadata/hooks/before-create-one-relation.hook';
|
||||
|
||||
import { RelationType } from './relation-metadata.dto';
|
||||
|
||||
@InputType()
|
||||
@BeforeCreateOne(BeforeCreateOneRelation)
|
||||
export class CreateRelationInput {
|
||||
@ -50,5 +51,6 @@ export class CreateRelationInput {
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
import { ObjectType, ID, Field, HideField } from '@nestjs/graphql';
|
||||
|
||||
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
import {
|
||||
Authorize,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
Relation,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { ObjectMetadataDTO } from 'src/metadata/object-metadata/dtos/object-metadata.dto';
|
||||
|
||||
export enum RelationType {
|
||||
ONE_TO_ONE = 'ONE_TO_ONE',
|
||||
ONE_TO_MANY = 'ONE_TO_MANY',
|
||||
MANY_TO_MANY = 'MANY_TO_MANY',
|
||||
}
|
||||
|
||||
@ObjectType('relation')
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableFilter: true,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@Relation('fromObjectMetadata', () => ObjectMetadataDTO)
|
||||
@Relation('toObjectMetadata', () => ObjectMetadataDTO)
|
||||
export class RelationMetadataDTO {
|
||||
@IDField(() => ID)
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
relationType: RelationType;
|
||||
|
||||
@Field()
|
||||
fromObjectMetadataId: string;
|
||||
|
||||
@Field()
|
||||
toObjectMetadataId: string;
|
||||
|
||||
@Field()
|
||||
fromFieldMetadataId: string;
|
||||
|
||||
@Field()
|
||||
toFieldMetadataId: string;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
|
||||
import { RelationMetadata } from './relation-metadata.entity';
|
||||
|
||||
import { RelationMetadataService } from './services/relation-metadata.service';
|
||||
import { CreateRelationInput } from './dtos/create-relation.input';
|
||||
|
||||
export const relationMetadataAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: RelationMetadata,
|
||||
DTOClass: RelationMetadata,
|
||||
ServiceClass: RelationMetadataService,
|
||||
CreateDTOClass: CreateRelationInput,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: { many: { disabled: true } },
|
||||
create: { many: { disabled: true } },
|
||||
update: { disabled: true },
|
||||
delete: { disabled: true },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
@ -1,93 +0,0 @@
|
||||
import { ObjectType, ID, Field } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import {
|
||||
Authorize,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
Relation,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
|
||||
export enum RelationType {
|
||||
ONE_TO_ONE = 'ONE_TO_ONE',
|
||||
ONE_TO_MANY = 'ONE_TO_MANY',
|
||||
MANY_TO_MANY = 'MANY_TO_MANY',
|
||||
}
|
||||
|
||||
@Entity('relationMetadata')
|
||||
@ObjectType('relation')
|
||||
@Authorize({
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.user?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableFilter: true,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@Relation('fromObjectMetadata', () => ObjectMetadata)
|
||||
@Relation('toObjectMetadata', () => ObjectMetadata)
|
||||
export class RelationMetadata {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false })
|
||||
relationType: RelationType;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
fromObjectMetadataId: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
toObjectMetadataId: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
fromFieldMetadataId: string;
|
||||
|
||||
@Field()
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
toFieldMetadataId: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => ObjectMetadata, (object) => object.fromRelations)
|
||||
fromObjectMetadata: ObjectMetadata;
|
||||
|
||||
@ManyToOne(() => ObjectMetadata, (object) => object.toRelations)
|
||||
toObjectMetadata: ObjectMetadata;
|
||||
|
||||
@OneToOne(() => FieldMetadata, (field) => field.fromRelationMetadata)
|
||||
@JoinColumn()
|
||||
fromFieldMetadata: FieldMetadata;
|
||||
|
||||
@OneToOne(() => FieldMetadata, (field) => field.toRelationMetadata)
|
||||
@JoinColumn()
|
||||
toFieldMetadata: FieldMetadata;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
@ -1,30 +1,52 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
NestjsQueryGraphQLModule,
|
||||
PagingStrategies,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FieldMetadataModule } from 'src/metadata/field-metadata/field-metadata.module';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
|
||||
import { TenantMigrationRunnerModule } from 'src/tenant-migration-runner/tenant-migration-runner.module';
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { RelationMetadataEntity } from 'src/database/typeorm/metadata/entities/relation-metadata.entity';
|
||||
|
||||
import { RelationMetadata } from './relation-metadata.entity';
|
||||
import { relationMetadataAutoResolverOpts } from './relation-metadata.auto-resolver-opts';
|
||||
import { RelationMetadataService } from './relation-metadata.service';
|
||||
|
||||
import { RelationMetadataService } from './services/relation-metadata.service';
|
||||
import { CreateRelationInput } from './dtos/create-relation.input';
|
||||
import { RelationMetadataDTO } from './dtos/relation-metadata.dto';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([RelationMetadata], 'metadata'),
|
||||
NestjsQueryTypeOrmModule.forFeature(
|
||||
[RelationMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
ObjectMetadataModule,
|
||||
FieldMetadataModule,
|
||||
MigrationRunnerModule,
|
||||
TenantMigrationRunnerModule,
|
||||
TenantMigrationModule,
|
||||
],
|
||||
services: [RelationMetadataService],
|
||||
resolvers: relationMetadataAutoResolverOpts,
|
||||
resolvers: [
|
||||
{
|
||||
EntityClass: RelationMetadataEntity,
|
||||
DTOClass: RelationMetadataDTO,
|
||||
ServiceClass: RelationMetadataService,
|
||||
CreateDTOClass: CreateRelationInput,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: { many: { disabled: true } },
|
||||
create: { many: { disabled: true } },
|
||||
update: { disabled: true },
|
||||
delete: { disabled: true },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
providers: [RelationMetadataService],
|
||||
|
||||
@ -8,34 +8,34 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
RelationMetadata,
|
||||
RelationType,
|
||||
} from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||
import { FieldMetadataService } from 'src/metadata/field-metadata/services/field-metadata.service';
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
import { FieldMetadataService } from 'src/metadata/field-metadata/field-metadata.service';
|
||||
import { CreateRelationInput } from 'src/metadata/relation-metadata/dtos/create-relation.input';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { TenantMigrationRunnerService } from 'src/tenant-migration-runner/tenant-migration-runner.service';
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { TenantMigrationColumnActionType } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
import {
|
||||
RelationMetadataEntity,
|
||||
RelationType,
|
||||
} from 'src/database/typeorm/metadata/entities/relation-metadata.entity';
|
||||
import { FieldMetadataType } from 'src/database/typeorm/metadata/entities/field-metadata.entity';
|
||||
import { TenantMigrationColumnActionType } from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
@Injectable()
|
||||
export class RelationMetadataService extends TypeOrmQueryService<RelationMetadata> {
|
||||
export class RelationMetadataService extends TypeOrmQueryService<RelationMetadataEntity> {
|
||||
constructor(
|
||||
@InjectRepository(RelationMetadata, 'metadata')
|
||||
private readonly relationMetadataRepository: Repository<RelationMetadata>,
|
||||
@InjectRepository(RelationMetadataEntity, 'metadata')
|
||||
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
private readonly migrationRunnerService: MigrationRunnerService,
|
||||
private readonly migrationRunnerService: TenantMigrationRunnerService,
|
||||
) {
|
||||
super(relationMetadataRepository);
|
||||
}
|
||||
|
||||
override async createOne(
|
||||
record: CreateRelationInput,
|
||||
): Promise<RelationMetadata> {
|
||||
): Promise<RelationMetadataEntity> {
|
||||
if (record.relationType === RelationType.MANY_TO_MANY) {
|
||||
throw new BadRequestException(
|
||||
'Many to many relations are not supported yet',
|
||||
@ -72,7 +72,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
targetColumnMap: {},
|
||||
isActive: true,
|
||||
type: FieldMetadataType.RELATION,
|
||||
objectId: record.fromObjectMetadataId,
|
||||
objectMetadataId: record.fromObjectMetadataId,
|
||||
workspaceId: record.workspaceId,
|
||||
},
|
||||
// NOTE: Since we have to create the field-metadata for the user, we need to use the toObjectMetadata info.
|
||||
@ -87,13 +87,13 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
targetColumnMap: {},
|
||||
isActive: true,
|
||||
type: FieldMetadataType.RELATION,
|
||||
objectId: record.toObjectMetadataId,
|
||||
objectMetadataId: record.toObjectMetadataId,
|
||||
workspaceId: record.workspaceId,
|
||||
},
|
||||
]);
|
||||
|
||||
const createdFieldMap = createdFields.reduce((acc, curr) => {
|
||||
acc[curr.objectId] = curr;
|
||||
acc[curr.objectMetadataId] = curr;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
const companiesMetadata = {
|
||||
nameSingular: 'companyV2',
|
||||
namePlural: 'companiesV2',
|
||||
labelSingular: 'Company',
|
||||
labelPlural: 'Companies',
|
||||
targetTableName: 'company',
|
||||
description: 'A company',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
fields: [
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
targetColumnMap: {
|
||||
value: 'name',
|
||||
},
|
||||
description: 'Name of the company',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'domainName',
|
||||
label: 'Domain Name',
|
||||
targetColumnMap: {
|
||||
value: 'domainName',
|
||||
},
|
||||
description: 'Domain name of the company',
|
||||
icon: 'IconLink',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'address',
|
||||
label: 'Address',
|
||||
targetColumnMap: {
|
||||
value: 'address',
|
||||
},
|
||||
description: 'Address of the company',
|
||||
icon: 'IconMap',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'NUMBER',
|
||||
name: 'employees',
|
||||
label: 'Employees',
|
||||
targetColumnMap: {
|
||||
value: 'employees',
|
||||
},
|
||||
description: 'Number of employees',
|
||||
icon: 'IconUsers',
|
||||
isNullable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default companiesMetadata;
|
||||
@ -1,13 +0,0 @@
|
||||
import companiesMetadata from './companies/companies.metadata';
|
||||
import viewFieldsMetadata from './view-fields/view-fields.metadata';
|
||||
import viewFiltersMetadata from './view-filters/view-filters.metadata';
|
||||
import viewSortsMetadata from './view-sorts/view-sorts.metadata';
|
||||
import viewsMetadata from './views/views.metadata';
|
||||
|
||||
export const standardObjectsMetadata = {
|
||||
companyV2: companiesMetadata,
|
||||
viewV2: viewsMetadata,
|
||||
viewFieldV2: viewFieldsMetadata,
|
||||
viewFilterV2: viewFiltersMetadata,
|
||||
viewSortV2: viewSortsMetadata,
|
||||
};
|
||||
@ -1,68 +0,0 @@
|
||||
const viewFieldsMetadata = {
|
||||
nameSingular: 'viewFieldV2',
|
||||
namePlural: 'viewFieldsV2',
|
||||
labelSingular: 'View Field',
|
||||
labelPlural: 'View Fields',
|
||||
targetTableName: 'viewField',
|
||||
description: '(System) View Fields',
|
||||
icon: 'IconColumns3',
|
||||
fields: [
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'fieldId',
|
||||
label: 'Field Id',
|
||||
targetColumnMap: {
|
||||
value: 'fieldId',
|
||||
},
|
||||
description: 'View Field target field',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'viewId',
|
||||
label: 'View Id',
|
||||
targetColumnMap: {
|
||||
value: 'viewId',
|
||||
},
|
||||
description: 'View Field related view',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN',
|
||||
name: 'isVisible',
|
||||
label: 'Visible',
|
||||
targetColumnMap: {
|
||||
value: 'isVisible',
|
||||
},
|
||||
description: 'View Field visibility',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'NUMBER',
|
||||
name: 'size',
|
||||
label: 'Size',
|
||||
targetColumnMap: {
|
||||
value: 'size',
|
||||
},
|
||||
description: 'View Field size',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'NUMBER',
|
||||
name: 'position',
|
||||
label: 'Position',
|
||||
targetColumnMap: {
|
||||
value: 'position',
|
||||
},
|
||||
description: 'View Field position',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default viewFieldsMetadata;
|
||||
@ -1,68 +0,0 @@
|
||||
const viewFiltersMetadata = {
|
||||
nameSingular: 'viewFilterV2',
|
||||
namePlural: 'viewFiltersV2',
|
||||
labelSingular: 'View Filter',
|
||||
labelPlural: 'View Filters',
|
||||
targetTableName: 'viewFilter',
|
||||
description: '(System) View Filters',
|
||||
icon: 'IconFilterBolt',
|
||||
fields: [
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'fieldId',
|
||||
label: 'Field Id',
|
||||
targetColumnMap: {
|
||||
value: 'fieldId',
|
||||
},
|
||||
description: 'View Filter target field',
|
||||
icon: null,
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'viewId',
|
||||
label: 'View Id',
|
||||
targetColumnMap: {
|
||||
value: 'viewId',
|
||||
},
|
||||
description: 'View Filter related view',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'operand',
|
||||
label: 'Operand',
|
||||
targetColumnMap: {
|
||||
value: 'operand',
|
||||
},
|
||||
description: 'View Filter operand',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'value',
|
||||
label: 'Value',
|
||||
targetColumnMap: {
|
||||
value: 'value',
|
||||
},
|
||||
description: 'View Filter value',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'displayValue',
|
||||
label: 'Display Value',
|
||||
targetColumnMap: {
|
||||
value: 'displayValue',
|
||||
},
|
||||
description: 'View Filter Display Value',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default viewFiltersMetadata;
|
||||
@ -1,46 +0,0 @@
|
||||
const viewSortsMetadata = {
|
||||
nameSingular: 'viewSortV2',
|
||||
namePlural: 'viewSortsV2',
|
||||
labelSingular: 'View Sort',
|
||||
labelPlural: 'View Sorts',
|
||||
targetTableName: 'viewSort',
|
||||
description: '(System) View Sorts',
|
||||
icon: 'IconArrowsSort',
|
||||
fields: [
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'fieldId',
|
||||
label: 'Field Id',
|
||||
targetColumnMap: {
|
||||
value: 'fieldId',
|
||||
},
|
||||
description: 'View Sort target field',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'viewId',
|
||||
label: 'View Id',
|
||||
targetColumnMap: {
|
||||
value: 'viewId',
|
||||
},
|
||||
description: 'View Sort related view',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'direction',
|
||||
label: 'Direction',
|
||||
targetColumnMap: {
|
||||
value: 'direction',
|
||||
},
|
||||
description: 'View Sort direction',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default viewSortsMetadata;
|
||||
@ -1,46 +0,0 @@
|
||||
const viewsMetadata = {
|
||||
nameSingular: 'viewV2',
|
||||
namePlural: 'viewsV2',
|
||||
labelSingular: 'View',
|
||||
labelPlural: 'Views',
|
||||
targetTableName: 'view',
|
||||
description: '(System) Views',
|
||||
icon: 'IconLayoutCollage',
|
||||
fields: [
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
targetColumnMap: {
|
||||
value: 'name',
|
||||
},
|
||||
description: 'View name',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'objectId',
|
||||
label: 'Object Id',
|
||||
targetColumnMap: {
|
||||
value: 'objectId',
|
||||
},
|
||||
description: 'View target object',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'type',
|
||||
label: 'Type',
|
||||
targetColumnMap: {
|
||||
value: 'type',
|
||||
},
|
||||
description: 'View type',
|
||||
icon: null,
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default viewsMetadata;
|
||||
@ -1,269 +0,0 @@
|
||||
import { DataSource, EntityManager } from 'typeorm';
|
||||
|
||||
export const standardObjectsPrefillData = async (
|
||||
workspaceDataSource: DataSource,
|
||||
schemaName: string,
|
||||
) => {
|
||||
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
|
||||
const createdCompanies = await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.company`, [
|
||||
'name',
|
||||
'domainName',
|
||||
'address',
|
||||
'employees',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
name: 'Airbnb',
|
||||
domainName: 'airbnb.com',
|
||||
address: 'San Francisco',
|
||||
employees: 5000,
|
||||
},
|
||||
{
|
||||
name: 'Qonto',
|
||||
domainName: 'qonto.com',
|
||||
address: 'San Francisco',
|
||||
employees: 800,
|
||||
},
|
||||
{
|
||||
name: 'Stripe',
|
||||
domainName: 'stripe.com',
|
||||
address: 'San Francisco',
|
||||
employees: 8000,
|
||||
},
|
||||
{
|
||||
name: 'Figma',
|
||||
domainName: 'figma.com',
|
||||
address: 'San Francisco',
|
||||
employees: 800,
|
||||
},
|
||||
{
|
||||
name: 'Notion',
|
||||
domainName: 'notion.com',
|
||||
address: 'San Francisco',
|
||||
employees: 400,
|
||||
},
|
||||
])
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
const companyIdMap = createdCompanies.raw.reduce((acc, view) => {
|
||||
acc[view.name] = view.id;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const createdViews = await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.view`, ['name', 'objectId', 'type'])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
name: 'All companies',
|
||||
objectId: 'company',
|
||||
type: 'table',
|
||||
},
|
||||
{
|
||||
name: 'All people',
|
||||
objectId: 'person',
|
||||
type: 'table',
|
||||
},
|
||||
{
|
||||
name: 'All opportunities',
|
||||
objectId: 'company',
|
||||
type: 'kanban',
|
||||
},
|
||||
{
|
||||
name: 'All Companies (V2)',
|
||||
objectId: companyIdMap['Airbnb'],
|
||||
type: 'table',
|
||||
},
|
||||
])
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
||||
acc[view.name] = view.id;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.viewField`, [
|
||||
'fieldId',
|
||||
'viewId',
|
||||
'position',
|
||||
'isVisible',
|
||||
'size',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
fieldId: 'name',
|
||||
viewId: viewIdMap['All Companies (V2)'],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 180,
|
||||
},
|
||||
{
|
||||
fieldId: 'name',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 180,
|
||||
},
|
||||
{
|
||||
fieldId: 'domainName',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
fieldId: 'accountOwner',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'createdAt',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'employees',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'linkedin',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
{
|
||||
fieldId: 'address',
|
||||
viewId: viewIdMap['All companies'],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
{
|
||||
fieldId: 'displayName',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldId: 'email',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'company',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'phone',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'createdAt',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'city',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'jobTitle',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'linkedin',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'x',
|
||||
viewId: viewIdMap['All people'],
|
||||
position: 8,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'amount',
|
||||
viewId: viewIdMap['All opportunities'],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 180,
|
||||
},
|
||||
{
|
||||
fieldId: 'probability',
|
||||
viewId: viewIdMap['All opportunities'],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'closeDate',
|
||||
viewId: viewIdMap['All opportunities'],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
fieldId: 'company',
|
||||
viewId: viewIdMap['All opportunities'],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'createdAt',
|
||||
viewId: viewIdMap['All opportunities'],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldId: 'pointOfContact',
|
||||
viewId: viewIdMap['All opportunities'],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
});
|
||||
};
|
||||
@ -1,24 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { MigrationRunnerModule } from 'src/metadata/migration-runner/migration-runner.module';
|
||||
import { TenantMigrationModule } from 'src/metadata/tenant-migration/tenant-migration.module';
|
||||
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
import { FieldMetadataModule } from 'src/metadata/field-metadata/field-metadata.module';
|
||||
|
||||
import { TenantInitialisationService } from './tenant-initialisation.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DataSourceModule,
|
||||
TenantMigrationModule,
|
||||
MigrationRunnerModule,
|
||||
ObjectMetadataModule,
|
||||
FieldMetadataModule,
|
||||
DataSourceMetadataModule,
|
||||
],
|
||||
exports: [TenantInitialisationService],
|
||||
providers: [TenantInitialisationService],
|
||||
})
|
||||
export class TenantInitialisationModule {}
|
||||
@ -1,90 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||
import { DataSourceMetadata } from 'src/metadata/data-source-metadata/data-source-metadata.entity';
|
||||
import { FieldMetadataService } from 'src/metadata/field-metadata/services/field-metadata.service';
|
||||
|
||||
import { standardObjectsPrefillData } from './standard-objects-prefill-data/standard-objects-prefill-data';
|
||||
|
||||
@Injectable()
|
||||
export class TenantInitialisationService {
|
||||
constructor(
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly tenantMigrationService: TenantMigrationService,
|
||||
private readonly migrationRunnerService: MigrationRunnerService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
private readonly dataSourceMetadataService: DataSourceMetadataService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Init a workspace by creating a new data source and running all migrations
|
||||
* @param workspaceId
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
public async init(workspaceId: string): Promise<void> {
|
||||
const schemaName = await this.dataSourceService.createWorkspaceSchema(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceMetadataService.createDataSourceMetadata(
|
||||
workspaceId,
|
||||
schemaName,
|
||||
);
|
||||
|
||||
await this.tenantMigrationService.insertStandardMigrations(workspaceId);
|
||||
|
||||
// Todo: keep in mind that we don't handle concurrency issues such as migrations being created at the same time
|
||||
// but it shouldn't be the role of this service to handle this kind of issues for now.
|
||||
// To check later when we run this in a job.
|
||||
await this.migrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.objectMetadataService.createStandardObjectsAndFieldsMetadata(
|
||||
dataSourceMetadata.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.prefillWorkspaceWithStandardObjects(
|
||||
dataSourceMetadata,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* We are prefilling a few standard objects with data to make it easier for the user to get started.
|
||||
*
|
||||
* @param dataSourceMetadata
|
||||
* @param workspaceId
|
||||
*/
|
||||
private async prefillWorkspaceWithStandardObjects(
|
||||
dataSourceMetadata: DataSourceMetadata,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const workspaceDataSource =
|
||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Could not connect to workspace data source');
|
||||
}
|
||||
|
||||
standardObjectsPrefillData(workspaceDataSource, dataSourceMetadata.schema);
|
||||
}
|
||||
|
||||
public async delete(workspaceId: string): Promise<void> {
|
||||
// Delete data from metadata tables
|
||||
await this.fieldMetadataService.deleteFieldsMetadata(workspaceId);
|
||||
await this.objectMetadataService.deleteObjectsMetadata(workspaceId);
|
||||
await this.tenantMigrationService.delete(workspaceId);
|
||||
await this.dataSourceMetadataService.delete(workspaceId);
|
||||
// Delete schema
|
||||
await this.dataSourceService.deleteWorkspaceSchema(workspaceId);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
TenantMigrationColumnActionType,
|
||||
TenantMigrationTableAction,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
export const addCompanyTable: TenantMigrationTableAction[] = [
|
||||
{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
TenantMigrationColumnActionType,
|
||||
TenantMigrationTableAction,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
export const addViewTable: TenantMigrationTableAction[] = [
|
||||
{
|
||||
@ -18,7 +18,7 @@ export const addViewTable: TenantMigrationTableAction[] = [
|
||||
action: TenantMigrationColumnActionType.CREATE,
|
||||
},
|
||||
{
|
||||
columnName: 'objectId',
|
||||
columnName: 'objectMetadataId',
|
||||
columnType: 'varchar',
|
||||
action: TenantMigrationColumnActionType.CREATE,
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
TenantMigrationColumnActionType,
|
||||
TenantMigrationTableAction,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
export const addViewFieldTable: TenantMigrationTableAction[] = [
|
||||
{
|
||||
@ -13,7 +13,7 @@ export const addViewFieldTable: TenantMigrationTableAction[] = [
|
||||
action: 'alter',
|
||||
columns: [
|
||||
{
|
||||
columnName: 'fieldId',
|
||||
columnName: 'fieldMetadataId',
|
||||
columnType: 'varchar',
|
||||
action: TenantMigrationColumnActionType.CREATE,
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
TenantMigrationColumnActionType,
|
||||
TenantMigrationTableAction,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
export const addViewFilterTable: TenantMigrationTableAction[] = [
|
||||
{
|
||||
@ -13,7 +13,7 @@ export const addViewFilterTable: TenantMigrationTableAction[] = [
|
||||
action: 'alter',
|
||||
columns: [
|
||||
{
|
||||
columnName: 'fieldId',
|
||||
columnName: 'fieldMetadataId',
|
||||
columnType: 'varchar',
|
||||
action: TenantMigrationColumnActionType.CREATE,
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
TenantMigrationColumnActionType,
|
||||
TenantMigrationTableAction,
|
||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||
TenantMigrationColumnActionType,
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
export const addViewSortTable: TenantMigrationTableAction[] = [
|
||||
{
|
||||
@ -13,7 +13,7 @@ export const addViewSortTable: TenantMigrationTableAction[] = [
|
||||
action: 'alter',
|
||||
columns: [
|
||||
{
|
||||
columnName: 'fieldId',
|
||||
columnName: 'fieldMetadataId',
|
||||
columnType: 'varchar',
|
||||
action: TenantMigrationColumnActionType.CREATE,
|
||||
},
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
export enum TenantMigrationColumnActionType {
|
||||
CREATE = 'CREATE',
|
||||
RELATION = 'RELATION',
|
||||
}
|
||||
|
||||
export type TenantMigrationColumnCreate = {
|
||||
action: TenantMigrationColumnActionType.CREATE;
|
||||
columnName: string;
|
||||
columnType: string;
|
||||
};
|
||||
|
||||
export type TenantMigrationColumnRelation = {
|
||||
action: TenantMigrationColumnActionType.RELATION;
|
||||
columnName: string;
|
||||
referencedTableName: string;
|
||||
referencedTableColumnName: string;
|
||||
};
|
||||
|
||||
export type TenantMigrationColumnAction = {
|
||||
action: TenantMigrationColumnActionType;
|
||||
} & (TenantMigrationColumnCreate | TenantMigrationColumnRelation);
|
||||
|
||||
export type TenantMigrationTableAction = {
|
||||
name: string;
|
||||
action: 'create' | 'alter';
|
||||
columns?: TenantMigrationColumnAction[];
|
||||
};
|
||||
@Entity('tenant_migrations')
|
||||
export class TenantMigration {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
migrations: TenantMigrationTableAction[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
name: string;
|
||||
|
||||
@Column({ default: false })
|
||||
isCustom: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
appliedAt?: Date;
|
||||
|
||||
@Column()
|
||||
workspaceId: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { TenantMigrationEntity } from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
import { TenantMigrationService } from './tenant-migration.service';
|
||||
import { TenantMigration } from './tenant-migration.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([TenantMigration], 'metadata')],
|
||||
imports: [TypeOrmModule.forFeature([TenantMigrationEntity], 'metadata')],
|
||||
exports: [TenantMigrationService],
|
||||
providers: [TenantMigrationService],
|
||||
})
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { TenantMigrationService } from './tenant-migration.service';
|
||||
import { TenantMigration } from './tenant-migration.entity';
|
||||
|
||||
describe('TenantMigrationService', () => {
|
||||
let service: TenantMigrationService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
TenantMigrationService,
|
||||
{
|
||||
provide: getRepositoryToken(TenantMigration, 'metadata'),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<TenantMigrationService>(TenantMigrationService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -4,16 +4,17 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
TenantMigration,
|
||||
TenantMigrationEntity,
|
||||
TenantMigrationTableAction,
|
||||
} from './tenant-migration.entity';
|
||||
} from 'src/database/typeorm/metadata/entities/tenant-migration.entity';
|
||||
|
||||
import { standardMigrations } from './standard-migrations';
|
||||
|
||||
@Injectable()
|
||||
export class TenantMigrationService {
|
||||
constructor(
|
||||
@InjectRepository(TenantMigration, 'metadata')
|
||||
private readonly tenantMigrationRepository: Repository<TenantMigration>,
|
||||
@InjectRepository(TenantMigrationEntity, 'metadata')
|
||||
private readonly tenantMigrationRepository: Repository<TenantMigrationEntity>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -60,7 +61,7 @@ export class TenantMigrationService {
|
||||
*/
|
||||
public async getPendingMigrations(
|
||||
workspaceId: string,
|
||||
): Promise<TenantMigration[]> {
|
||||
): Promise<TenantMigrationEntity[]> {
|
||||
return await this.tenantMigrationRepository.find({
|
||||
order: { createdAt: 'ASC' },
|
||||
where: {
|
||||
@ -79,7 +80,7 @@ export class TenantMigrationService {
|
||||
*/
|
||||
public async setAppliedAtForMigration(
|
||||
workspaceId: string,
|
||||
migration: TenantMigration,
|
||||
migration: TenantMigrationEntity,
|
||||
) {
|
||||
await this.tenantMigrationRepository.save({
|
||||
id: migration.id,
|
||||
|
||||
Reference in New Issue
Block a user