Add Tenant initialisation service (#2100)
* Add Tenant initialisation service * few fixes * fix constraint * fix tests * update metadata json with employees and address * add V2 * remove metadata.gql
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { AbilityFactory } from 'src/ability/ability.factory';
|
import { AbilityFactory } from 'src/ability/ability.factory';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
@ -129,7 +129,6 @@ import {
|
|||||||
ReadApiKeyAbilityHandler,
|
ReadApiKeyAbilityHandler,
|
||||||
} from './handlers/api-key.ability-handler';
|
} from './handlers/api-key.ability-handler';
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
AbilityFactory,
|
AbilityFactory,
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { ActivityResolver } from './resolvers/activity.resolver';
|
import { ActivityResolver } from './resolvers/activity.resolver';
|
||||||
import { ActivityService } from './services/activity.service';
|
import { ActivityService } from './services/activity.service';
|
||||||
import { ActivityTargetService } from './services/activity-target.service';
|
import { ActivityTargetService } from './services/activity-target.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [ActivityResolver, ActivityService, ActivityTargetService],
|
providers: [ActivityResolver, ActivityService, ActivityTargetService],
|
||||||
exports: [ActivityService, ActivityTargetService],
|
exports: [ActivityService, ActivityTargetService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,11 +2,14 @@ import { Module } from '@nestjs/common';
|
|||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
|
||||||
import { TokenService } from 'src/core/auth/services/token.service';
|
import { TokenService } from 'src/core/auth/services/token.service';
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { ApiKeyResolver } from './api-key.resolver';
|
import { ApiKeyResolver } from './api-key.resolver';
|
||||||
import { ApiKeyService } from './api-key.service';
|
import { ApiKeyService } from './api-key.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [ApiKeyResolver, ApiKeyService, TokenService, JwtService],
|
providers: [ApiKeyResolver, ApiKeyService, TokenService, JwtService],
|
||||||
})
|
})
|
||||||
export class ApiKeyModule {}
|
export class ApiKeyModule {}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { AttachmentResolver } from './resolvers/attachment.resolver';
|
import { AttachmentResolver } from './resolvers/attachment.resolver';
|
||||||
import { AttachmentService } from './services/attachment.service';
|
import { AttachmentService } from './services/attachment.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [AttachmentService, AttachmentResolver, FileUploadService],
|
providers: [AttachmentService, AttachmentResolver, FileUploadService],
|
||||||
exports: [AttachmentService],
|
exports: [AttachmentService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { CommentService } from './comment.service';
|
import { CommentService } from './comment.service';
|
||||||
import { CommentResolver } from './comment.resolver';
|
import { CommentResolver } from './comment.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [CommentService, CommentResolver],
|
providers: [CommentService, CommentResolver],
|
||||||
exports: [CommentService],
|
exports: [CommentService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,13 +2,15 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { CommentModule } from 'src/core/comment/comment.module';
|
import { CommentModule } from 'src/core/comment/comment.module';
|
||||||
import { ActivityModule } from 'src/core/activity/activity.module';
|
import { ActivityModule } from 'src/core/activity/activity.module';
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { CompanyService } from './company.service';
|
import { CompanyService } from './company.service';
|
||||||
import { CompanyResolver } from './company.resolver';
|
import { CompanyResolver } from './company.resolver';
|
||||||
import { CompanyRelationsResolver } from './company-relations.resolver';
|
import { CompanyRelationsResolver } from './company-relations.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CommentModule, ActivityModule],
|
imports: [CommentModule, ActivityModule, AbilityModule, PrismaModule],
|
||||||
providers: [CompanyService, CompanyResolver, CompanyRelationsResolver],
|
providers: [CompanyService, CompanyResolver, CompanyRelationsResolver],
|
||||||
exports: [CompanyService],
|
exports: [CompanyService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { FavoriteResolver } from './resolvers/favorite.resolver';
|
import { FavoriteResolver } from './resolvers/favorite.resolver';
|
||||||
import { FavoriteService } from './services/favorite.service';
|
import { FavoriteService } from './services/favorite.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [FavoriteService, FavoriteResolver],
|
providers: [FavoriteService, FavoriteResolver],
|
||||||
exports: [FavoriteService],
|
exports: [FavoriteService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,13 +3,21 @@ import { Module } from '@nestjs/common';
|
|||||||
import { CommentModule } from 'src/core/comment/comment.module';
|
import { CommentModule } from 'src/core/comment/comment.module';
|
||||||
import { ActivityModule } from 'src/core/activity/activity.module';
|
import { ActivityModule } from 'src/core/activity/activity.module';
|
||||||
import { FileModule } from 'src/core/file/file.module';
|
import { FileModule } from 'src/core/file/file.module';
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { PersonService } from './person.service';
|
import { PersonService } from './person.service';
|
||||||
import { PersonResolver } from './person.resolver';
|
import { PersonResolver } from './person.resolver';
|
||||||
import { PersonRelationsResolver } from './person-relations.resolver';
|
import { PersonRelationsResolver } from './person-relations.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CommentModule, ActivityModule, FileModule],
|
imports: [
|
||||||
|
CommentModule,
|
||||||
|
ActivityModule,
|
||||||
|
FileModule,
|
||||||
|
AbilityModule,
|
||||||
|
PrismaModule,
|
||||||
|
],
|
||||||
providers: [PersonService, PersonResolver, PersonRelationsResolver],
|
providers: [PersonService, PersonResolver, PersonRelationsResolver],
|
||||||
exports: [PersonService],
|
exports: [PersonService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { PipelineService } from './services/pipeline.service';
|
import { PipelineService } from './services/pipeline.service';
|
||||||
import { PipelineResolver } from './resolvers/pipeline.resolver';
|
import { PipelineResolver } from './resolvers/pipeline.resolver';
|
||||||
import { PipelineStageResolver } from './resolvers/pipeline-stage.resolver';
|
import { PipelineStageResolver } from './resolvers/pipeline-stage.resolver';
|
||||||
@ -8,7 +11,7 @@ import { PipelineStageService } from './services/pipeline-stage.service';
|
|||||||
import { PipelineProgressService } from './services/pipeline-progress.service';
|
import { PipelineProgressService } from './services/pipeline-progress.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [
|
providers: [
|
||||||
PipelineService,
|
PipelineService,
|
||||||
PipelineStageService,
|
PipelineStageService,
|
||||||
|
|||||||
@ -3,12 +3,20 @@ import { Module } from '@nestjs/common';
|
|||||||
import { FileModule } from 'src/core/file/file.module';
|
import { FileModule } from 'src/core/file/file.module';
|
||||||
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
import { WorkspaceModule } from 'src/core/workspace/workspace.module';
|
||||||
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
|
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { UserResolver } from './user.resolver';
|
import { UserResolver } from './user.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FileModule, WorkspaceModule, EnvironmentModule],
|
imports: [
|
||||||
|
FileModule,
|
||||||
|
WorkspaceModule,
|
||||||
|
EnvironmentModule,
|
||||||
|
AbilityModule,
|
||||||
|
PrismaModule,
|
||||||
|
],
|
||||||
providers: [UserService, UserResolver],
|
providers: [UserService, UserResolver],
|
||||||
exports: [UserService],
|
exports: [UserService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { ViewFieldService } from './services/view-field.service';
|
import { ViewFieldService } from './services/view-field.service';
|
||||||
import { ViewFieldResolver } from './resolvers/view-field.resolver';
|
import { ViewFieldResolver } from './resolvers/view-field.resolver';
|
||||||
import { ViewSortService } from './services/view-sort.service';
|
import { ViewSortService } from './services/view-sort.service';
|
||||||
@ -10,6 +13,7 @@ import { ViewFilterService } from './services/view-filter.service';
|
|||||||
import { ViewFilterResolver } from './resolvers/view-filter.resolver';
|
import { ViewFilterResolver } from './resolvers/view-filter.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [AbilityModule, PrismaModule],
|
||||||
providers: [
|
providers: [
|
||||||
ViewService,
|
ViewService,
|
||||||
ViewFieldService,
|
ViewFieldService,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { PersonService } from 'src/core/person/person.service';
|
|||||||
import { CompanyService } from 'src/core/company/company.service';
|
import { CompanyService } from 'src/core/company/company.service';
|
||||||
import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service';
|
import { PipelineProgressService } from 'src/core/pipeline/services/pipeline-progress.service';
|
||||||
import { ViewService } from 'src/core/view/services/view.service';
|
import { ViewService } from 'src/core/view/services/view.service';
|
||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service';
|
||||||
|
|
||||||
import { WorkspaceService } from './workspace.service';
|
import { WorkspaceService } from './workspace.service';
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ describe('WorkspaceService', () => {
|
|||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: DataSourceService,
|
provide: TenantInitialisationService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { PipelineService } from 'src/core/pipeline/services/pipeline.service';
|
|||||||
import { ViewService } from 'src/core/view/services/view.service';
|
import { ViewService } from 'src/core/view/services/view.service';
|
||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceService {
|
export class WorkspaceService {
|
||||||
@ -23,7 +23,7 @@ export class WorkspaceService {
|
|||||||
private readonly pipelineStageService: PipelineStageService,
|
private readonly pipelineStageService: PipelineStageService,
|
||||||
private readonly pipelineProgressService: PipelineProgressService,
|
private readonly pipelineProgressService: PipelineProgressService,
|
||||||
private readonly viewService: ViewService,
|
private readonly viewService: ViewService,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly tenantInitialisationService: TenantInitialisationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// Find
|
// Find
|
||||||
@ -66,7 +66,7 @@ export class WorkspaceService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create workspace schema
|
// Create workspace schema
|
||||||
await this.dataSourceService.createWorkspaceSchema(workspace.id);
|
await this.tenantInitialisationService.init(workspace.id);
|
||||||
|
|
||||||
// Create default companies
|
// Create default companies
|
||||||
const companies = await this.companyService.createDefaultCompanies({
|
const companies = await this.companyService.createDefaultCompanies({
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import { PipelineModule } from 'src/core/pipeline/pipeline.module';
|
|||||||
import { CompanyModule } from 'src/core/company/company.module';
|
import { CompanyModule } from 'src/core/company/company.module';
|
||||||
import { PersonModule } from 'src/core/person/person.module';
|
import { PersonModule } from 'src/core/person/person.module';
|
||||||
import { ViewModule } from 'src/core/view/view.module';
|
import { ViewModule } from 'src/core/view/view.module';
|
||||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
import { TenantInitialisationModule } from 'src/metadata/tenant-initialisation/tenant-initialisation.module';
|
||||||
|
import { AbilityModule } from 'src/ability/ability.module';
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
|
|
||||||
import { WorkspaceService } from './services/workspace.service';
|
import { WorkspaceService } from './services/workspace.service';
|
||||||
import { WorkspaceMemberService } from './services/workspace-member.service';
|
import { WorkspaceMemberService } from './services/workspace-member.service';
|
||||||
@ -14,11 +16,13 @@ import { WorkspaceResolver } from './resolvers/workspace.resolver';
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
AbilityModule,
|
||||||
PipelineModule,
|
PipelineModule,
|
||||||
CompanyModule,
|
CompanyModule,
|
||||||
PersonModule,
|
PersonModule,
|
||||||
ViewModule,
|
ViewModule,
|
||||||
DataSourceModule,
|
TenantInitialisationModule,
|
||||||
|
PrismaModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
@Global()
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [PrismaService],
|
providers: [PrismaService],
|
||||||
exports: [PrismaService],
|
exports: [PrismaService],
|
||||||
|
|||||||
@ -33,14 +33,6 @@ export const seedWorkspaces = async (prisma: PrismaClient) => {
|
|||||||
'80f5e1e3-574a-4bf9-b5bc-98aedd2b76e6', 'workspace_twenty', 'postgres', 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420'
|
'80f5e1e3-574a-4bf9-b5bc-98aedd2b76e6', 'workspace_twenty', 'postgres', 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420'
|
||||||
) ON CONFLICT DO NOTHING`,
|
) ON CONFLICT DO NOTHING`,
|
||||||
);
|
);
|
||||||
await prisma.$queryRawUnsafe(`
|
|
||||||
CREATE TABLE IF NOT EXISTS workspace_twenty.tenant_migrations (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
migrations JSONB,
|
|
||||||
applied_at TIMESTAMP NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
|
|
||||||
await prisma.$queryRawUnsafe(
|
await prisma.$queryRawUnsafe(
|
||||||
'CREATE SCHEMA IF NOT EXISTS workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd',
|
'CREATE SCHEMA IF NOT EXISTS workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd',
|
||||||
@ -53,12 +45,4 @@ export const seedWorkspaces = async (prisma: PrismaClient) => {
|
|||||||
'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1', 'workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd', 'postgres', 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
|
'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1', 'workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd', 'postgres', 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419'
|
||||||
) ON CONFLICT DO NOTHING`,
|
) ON CONFLICT DO NOTHING`,
|
||||||
);
|
);
|
||||||
await prisma.$queryRawUnsafe(`
|
|
||||||
CREATE TABLE IF NOT EXISTS workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.tenant_migrations (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
migrations JSONB,
|
|
||||||
applied_at TIMESTAMP NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT NOW()
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TerminusModule } from '@nestjs/terminus';
|
import { TerminusModule } from '@nestjs/terminus';
|
||||||
|
|
||||||
|
import { PrismaModule } from 'src/database/prisma.module';
|
||||||
import { HealthController } from 'src/health/health.controller';
|
import { HealthController } from 'src/health/health.controller';
|
||||||
import { PrismaHealthIndicator } from 'src/health/indicators/prisma-health-indicator';
|
import { PrismaHealthIndicator } from 'src/health/indicators/prisma-health-indicator';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TerminusModule],
|
imports: [TerminusModule, PrismaModule],
|
||||||
controllers: [HealthController],
|
controllers: [HealthController],
|
||||||
providers: [PrismaHealthIndicator],
|
providers: [PrismaHealthIndicator],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||||
|
|
||||||
import { DataSource, QueryRunner, Table } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
import { EnvironmentService } from 'src/integrations/environment/environment.service';
|
||||||
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||||
@ -37,55 +37,14 @@ export class DataSourceService implements OnModuleInit, OnModuleDestroy {
|
|||||||
const schemaAlreadyExists = await queryRunner.hasSchema(schemaName);
|
const schemaAlreadyExists = await queryRunner.hasSchema(schemaName);
|
||||||
|
|
||||||
if (schemaAlreadyExists) {
|
if (schemaAlreadyExists) {
|
||||||
return schemaName;
|
throw new Error(`Schema ${schemaName} already exists`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await queryRunner.createSchema(schemaName, true);
|
await queryRunner.createSchema(schemaName, true);
|
||||||
await this.createMigrationTable(queryRunner, schemaName);
|
|
||||||
await queryRunner.release();
|
|
||||||
|
|
||||||
await this.dataSourceMetadataService.createDataSourceMetadata(
|
|
||||||
workspaceId,
|
|
||||||
schemaName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return schemaName;
|
return schemaName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createMigrationTable(
|
|
||||||
queryRunner: QueryRunner,
|
|
||||||
schemaName: string,
|
|
||||||
) {
|
|
||||||
await queryRunner.createTable(
|
|
||||||
new Table({
|
|
||||||
name: 'tenant_migrations',
|
|
||||||
schema: schemaName,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'id',
|
|
||||||
type: 'uuid',
|
|
||||||
isPrimary: true,
|
|
||||||
default: 'uuid_generate_v4()',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'migrations',
|
|
||||||
type: 'jsonb',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'applied_at',
|
|
||||||
type: 'timestamp',
|
|
||||||
isNullable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'created_at',
|
|
||||||
type: 'timestamp',
|
|
||||||
default: 'now()',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects to a workspace data source using the workspace metadata. Returns a cached connection if it exists.
|
* Connects to a workspace data source using the workspace metadata. Returns a cached connection if it exists.
|
||||||
* @param workspaceId
|
* @param workspaceId
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
|
Unique,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import {
|
import {
|
||||||
@ -38,6 +39,7 @@ export type FieldMetadataTargetColumnMap = {
|
|||||||
disableFilter: true,
|
disableFilter: true,
|
||||||
disableSort: true,
|
disableSort: true,
|
||||||
})
|
})
|
||||||
|
@Unique('IndexOnNameAndWorkspaceIdUnique', ['name', 'objectId', 'workspaceId'])
|
||||||
export class FieldMetadata {
|
export class FieldMetadata {
|
||||||
@IDField(() => ID)
|
@IDField(() => ID)
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
|||||||
@ -10,13 +10,13 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
|||||||
|
|
||||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||||
import {
|
import {
|
||||||
convertFieldMetadataToColumnChanges,
|
convertFieldMetadataToColumnActions,
|
||||||
generateTargetColumnMap,
|
generateTargetColumnMap,
|
||||||
} from 'src/metadata/field-metadata/utils/field-metadata.util';
|
} from 'src/metadata/field-metadata/utils/field-metadata.util';
|
||||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||||
import { TenantMigrationTableChange } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
||||||
@ -59,13 +59,16 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadata> {
|
|||||||
targetColumnMap: generateTargetColumnMap(record.type),
|
targetColumnMap: generateTargetColumnMap(record.type),
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.tenantMigrationService.createMigration(record.workspaceId, [
|
await this.tenantMigrationService.createCustomMigration(
|
||||||
{
|
record.workspaceId,
|
||||||
name: objectMetadata.targetTableName,
|
[
|
||||||
change: 'alter',
|
{
|
||||||
columns: convertFieldMetadataToColumnChanges(createdFieldMetadata),
|
name: objectMetadata.targetTableName,
|
||||||
} satisfies TenantMigrationTableChange,
|
action: 'alter',
|
||||||
]);
|
columns: convertFieldMetadataToColumnActions(createdFieldMetadata),
|
||||||
|
} satisfies TenantMigrationTableAction,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
await this.migrationRunnerService.executeMigrationFromPendingMigrations(
|
await this.migrationRunnerService.executeMigrationFromPendingMigrations(
|
||||||
record.workspaceId,
|
record.workspaceId,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
FieldMetadata,
|
FieldMetadata,
|
||||||
FieldMetadataTargetColumnMap,
|
FieldMetadataTargetColumnMap,
|
||||||
} from 'src/metadata/field-metadata/field-metadata.entity';
|
} from 'src/metadata/field-metadata/field-metadata.entity';
|
||||||
import { TenantMigrationColumnChange } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
import { TenantMigrationColumnAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a column name from a field name removing unsupported characters.
|
* Generate a column name from a field name removing unsupported characters.
|
||||||
@ -52,15 +52,15 @@ export function generateTargetColumnMap(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertFieldMetadataToColumnChanges(
|
export function convertFieldMetadataToColumnActions(
|
||||||
fieldMetadata: FieldMetadata,
|
fieldMetadata: FieldMetadata,
|
||||||
): TenantMigrationColumnChange[] {
|
): TenantMigrationColumnAction[] {
|
||||||
switch (fieldMetadata.type) {
|
switch (fieldMetadata.type) {
|
||||||
case 'text':
|
case 'text':
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.value,
|
name: fieldMetadata.targetColumnMap.value,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -69,7 +69,7 @@ export function convertFieldMetadataToColumnChanges(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.value,
|
name: fieldMetadata.targetColumnMap.value,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -77,7 +77,7 @@ export function convertFieldMetadataToColumnChanges(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.value,
|
name: fieldMetadata.targetColumnMap.value,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -85,7 +85,7 @@ export function convertFieldMetadataToColumnChanges(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.value,
|
name: fieldMetadata.targetColumnMap.value,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -93,7 +93,7 @@ export function convertFieldMetadataToColumnChanges(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.value,
|
name: fieldMetadata.targetColumnMap.value,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'timestamp',
|
type: 'timestamp',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -101,12 +101,12 @@ export function convertFieldMetadataToColumnChanges(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.text,
|
name: fieldMetadata.targetColumnMap.text,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.link,
|
name: fieldMetadata.targetColumnMap.link,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -114,12 +114,12 @@ export function convertFieldMetadataToColumnChanges(
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.amount,
|
name: fieldMetadata.targetColumnMap.amount,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: fieldMetadata.targetColumnMap.currency,
|
name: fieldMetadata.targetColumnMap.currency,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import { MetadataNameLabelRefactoring1697126636202 } from './migrations/16971266
|
|||||||
import { RemoveFieldMetadataPlaceholder1697471445015 } from './migrations/1697471445015-removeFieldMetadataPlaceholder';
|
import { RemoveFieldMetadataPlaceholder1697471445015 } from './migrations/1697471445015-removeFieldMetadataPlaceholder';
|
||||||
import { AddSoftDelete1697474804403 } from './migrations/1697474804403-addSoftDelete';
|
import { AddSoftDelete1697474804403 } from './migrations/1697474804403-addSoftDelete';
|
||||||
import { RemoveSingularPluralFromFieldLabelAndName1697534910933 } from './migrations/1697534910933-removeSingularPluralFromFieldLabelAndName';
|
import { RemoveSingularPluralFromFieldLabelAndName1697534910933 } from './migrations/1697534910933-removeSingularPluralFromFieldLabelAndName';
|
||||||
|
import { AddNameAndIsCustomToTenantMigration1697622715467 } from './migrations/1697622715467-addNameAndIsCustomToTenantMigration';
|
||||||
|
import { AddUniqueConstraintsOnFieldObjectMetadata1697630766924 } from './migrations/1697630766924-addUniqueConstraintsOnFieldObjectMetadata';
|
||||||
|
|
||||||
config();
|
config();
|
||||||
|
|
||||||
@ -33,6 +35,8 @@ export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = {
|
|||||||
RemoveFieldMetadataPlaceholder1697471445015,
|
RemoveFieldMetadataPlaceholder1697471445015,
|
||||||
AddSoftDelete1697474804403,
|
AddSoftDelete1697474804403,
|
||||||
RemoveSingularPluralFromFieldLabelAndName1697534910933,
|
RemoveSingularPluralFromFieldLabelAndName1697534910933,
|
||||||
|
AddNameAndIsCustomToTenantMigration1697622715467,
|
||||||
|
AddUniqueConstraintsOnFieldObjectMetadata1697630766924,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import { QueryRunner, Table, TableColumn } from 'typeorm';
|
|||||||
|
|
||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||||
import {
|
import {
|
||||||
TenantMigrationTableChange,
|
TenantMigrationTableAction,
|
||||||
TenantMigrationColumnChange,
|
TenantMigrationColumnAction,
|
||||||
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
} from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||||
|
|
||||||
@ -20,11 +20,11 @@ export class MigrationRunnerService {
|
|||||||
* Executes pending migrations for a given workspace
|
* Executes pending migrations for a given workspace
|
||||||
*
|
*
|
||||||
* @param workspaceId string
|
* @param workspaceId string
|
||||||
* @returns Promise<TenantMigrationTableChange[]>
|
* @returns Promise<TenantMigrationTableAction[]>
|
||||||
*/
|
*/
|
||||||
public async executeMigrationFromPendingMigrations(
|
public async executeMigrationFromPendingMigrations(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<TenantMigrationTableChange[]> {
|
): Promise<TenantMigrationTableAction[]> {
|
||||||
const workspaceDataSource =
|
const workspaceDataSource =
|
||||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export class MigrationRunnerService {
|
|||||||
const pendingMigrations =
|
const pendingMigrations =
|
||||||
await this.tenantMigrationService.getPendingMigrations(workspaceId);
|
await this.tenantMigrationService.getPendingMigrations(workspaceId);
|
||||||
|
|
||||||
const flattenedPendingMigrations: TenantMigrationTableChange[] =
|
const flattenedPendingMigrations: TenantMigrationTableAction[] =
|
||||||
pendingMigrations.reduce((acc, pendingMigration) => {
|
pendingMigrations.reduce((acc, pendingMigration) => {
|
||||||
return [...acc, ...pendingMigration.migrations];
|
return [...acc, ...pendingMigration.migrations];
|
||||||
}, []);
|
}, []);
|
||||||
@ -71,9 +71,9 @@ export class MigrationRunnerService {
|
|||||||
private async handleTableChanges(
|
private async handleTableChanges(
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
tableMigration: TenantMigrationTableChange,
|
tableMigration: TenantMigrationTableAction,
|
||||||
) {
|
) {
|
||||||
switch (tableMigration.change) {
|
switch (tableMigration.action) {
|
||||||
case 'create':
|
case 'create':
|
||||||
await this.createTable(queryRunner, schemaName, tableMigration.name);
|
await this.createTable(queryRunner, schemaName, tableMigration.name);
|
||||||
break;
|
break;
|
||||||
@ -87,7 +87,7 @@ export class MigrationRunnerService {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Migration table change ${tableMigration.change} not supported`,
|
`Migration table action ${tableMigration.action} not supported`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,21 +142,21 @@ export class MigrationRunnerService {
|
|||||||
* @param queryRunner QueryRunner
|
* @param queryRunner QueryRunner
|
||||||
* @param schemaName string
|
* @param schemaName string
|
||||||
* @param tableName string
|
* @param tableName string
|
||||||
* @param columnMigrations TenantMigrationColumnChange[]
|
* @param columnMigrations TenantMigrationColumnAction[]
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
private async handleColumnChanges(
|
private async handleColumnChanges(
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
tableName: string,
|
tableName: string,
|
||||||
columnMigrations?: TenantMigrationColumnChange[],
|
columnMigrations?: TenantMigrationColumnAction[],
|
||||||
) {
|
) {
|
||||||
if (!columnMigrations || columnMigrations.length === 0) {
|
if (!columnMigrations || columnMigrations.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const columnMigration of columnMigrations) {
|
for (const columnMigration of columnMigrations) {
|
||||||
switch (columnMigration.change) {
|
switch (columnMigration.action) {
|
||||||
case 'create':
|
case 'create':
|
||||||
await this.createColumn(
|
await this.createColumn(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
@ -165,13 +165,9 @@ export class MigrationRunnerService {
|
|||||||
columnMigration,
|
columnMigration,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'alter':
|
|
||||||
throw new Error(
|
|
||||||
`Migration column change ${columnMigration.change} not supported`,
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Migration column change ${columnMigration.change} not supported`,
|
`Migration column action ${columnMigration.action} not supported`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,13 +179,13 @@ export class MigrationRunnerService {
|
|||||||
* @param queryRunner QueryRunner
|
* @param queryRunner QueryRunner
|
||||||
* @param schemaName string
|
* @param schemaName string
|
||||||
* @param tableName string
|
* @param tableName string
|
||||||
* @param migrationColumn TenantMigrationColumnChange
|
* @param migrationColumn TenantMigrationColumnAction
|
||||||
*/
|
*/
|
||||||
private async createColumn(
|
private async createColumn(
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
tableName: string,
|
tableName: string,
|
||||||
migrationColumn: TenantMigrationColumnChange,
|
migrationColumn: TenantMigrationColumnAction,
|
||||||
) {
|
) {
|
||||||
await queryRunner.addColumn(
|
await queryRunner.addColumn(
|
||||||
`${schemaName}.${tableName}`,
|
`${schemaName}.${tableName}`,
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
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`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
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")`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
Entity,
|
Entity,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
|
Unique,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import {
|
import {
|
||||||
@ -36,6 +37,11 @@ import { BeforeCreateOneObject } from './hooks/before-create-one-object.hook';
|
|||||||
disableSort: true,
|
disableSort: true,
|
||||||
})
|
})
|
||||||
@CursorConnection('fields', () => FieldMetadata)
|
@CursorConnection('fields', () => FieldMetadata)
|
||||||
|
@Unique('IndexOnNameSingularAndWorkspaceIdUnique', [
|
||||||
|
'nameSingular',
|
||||||
|
'workspaceId',
|
||||||
|
])
|
||||||
|
@Unique('IndexOnNamePluralAndWorkspaceIdUnique', ['namePlural', 'workspaceId'])
|
||||||
export class ObjectMetadata {
|
export class ObjectMetadata {
|
||||||
@IDField(() => ID)
|
@IDField(() => ID)
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
@ -46,11 +52,11 @@ export class ObjectMetadata {
|
|||||||
dataSourceId: string;
|
dataSourceId: string;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
@Column({ nullable: false, name: 'name_singular', unique: true })
|
@Column({ nullable: false, name: 'name_singular' })
|
||||||
nameSingular: string;
|
nameSingular: string;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
@Column({ nullable: false, name: 'name_plural', unique: true })
|
@Column({ nullable: false, name: 'name_plural' })
|
||||||
namePlural: string;
|
namePlural: string;
|
||||||
|
|
||||||
@Field()
|
@Field()
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { Repository } from 'typeorm';
|
|||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
|
|
||||||
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
import { TenantMigrationService } from 'src/metadata/tenant-migration/tenant-migration.service';
|
||||||
import { TenantMigrationTableChange } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||||
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
import { MigrationRunnerService } from 'src/metadata/migration-runner/migration-runner.service';
|
||||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||||
|
|
||||||
@ -24,13 +24,13 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadata> {
|
|||||||
override async createOne(record: ObjectMetadata): Promise<ObjectMetadata> {
|
override async createOne(record: ObjectMetadata): Promise<ObjectMetadata> {
|
||||||
const createdObjectMetadata = await super.createOne(record);
|
const createdObjectMetadata = await super.createOne(record);
|
||||||
|
|
||||||
await this.tenantMigrationService.createMigration(
|
await this.tenantMigrationService.createCustomMigration(
|
||||||
createdObjectMetadata.workspaceId,
|
createdObjectMetadata.workspaceId,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: createdObjectMetadata.targetTableName,
|
name: createdObjectMetadata.targetTableName,
|
||||||
change: 'create',
|
action: 'create',
|
||||||
} satisfies TenantMigrationTableChange,
|
} satisfies TenantMigrationTableAction,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"nameSingular": "companyV2",
|
||||||
|
"namePlural": "companiesV2",
|
||||||
|
"labelSingular": "Company",
|
||||||
|
"labelPlural": "Companies",
|
||||||
|
"targetTableName": "company",
|
||||||
|
"description": "A company",
|
||||||
|
"icon": "business",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "name",
|
||||||
|
"label": "Name",
|
||||||
|
"targetColumnMap": {
|
||||||
|
"value": "name"
|
||||||
|
},
|
||||||
|
"description": "Name of the company",
|
||||||
|
"icon": null,
|
||||||
|
"isNullable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "domainName",
|
||||||
|
"label": "Domain Name",
|
||||||
|
"targetColumnMap": {
|
||||||
|
"value": "domainName"
|
||||||
|
},
|
||||||
|
"description": "Domain name of the company",
|
||||||
|
"icon": "url",
|
||||||
|
"isNullable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "address",
|
||||||
|
"label": "Address",
|
||||||
|
"targetColumnMap": {
|
||||||
|
"value": "address"
|
||||||
|
},
|
||||||
|
"description": "Address of the company",
|
||||||
|
"icon": "location",
|
||||||
|
"isNullable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"name": "employees",
|
||||||
|
"label": "Employees",
|
||||||
|
"targetColumnMap": {
|
||||||
|
"value": "employees"
|
||||||
|
},
|
||||||
|
"description": "Number of employees",
|
||||||
|
"icon": "people",
|
||||||
|
"isNullable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import companyObject from './companies.metadata.json';
|
||||||
|
|
||||||
|
export const standardObjectsMetadata = {
|
||||||
|
companyV2: companyObject,
|
||||||
|
};
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
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 { FieldMetadataModule } from 'src/metadata/field-metadata/field-metadata.module';
|
||||||
|
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||||
|
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
|
||||||
|
|
||||||
|
import { TenantInitialisationService } from './tenant-initialisation.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
DataSourceModule,
|
||||||
|
TenantMigrationModule,
|
||||||
|
MigrationRunnerModule,
|
||||||
|
ObjectMetadataModule,
|
||||||
|
FieldMetadataModule,
|
||||||
|
DataSourceMetadataModule,
|
||||||
|
],
|
||||||
|
exports: [TenantInitialisationService],
|
||||||
|
providers: [TenantInitialisationService],
|
||||||
|
})
|
||||||
|
export class TenantInitialisationModule {}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
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 { FieldMetadataService } from 'src/metadata/field-metadata/services/field-metadata.service';
|
||||||
|
import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service';
|
||||||
|
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
|
||||||
|
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||||
|
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||||
|
|
||||||
|
import { standardObjectsMetadata } from './standard-objects/standard-object-metadata';
|
||||||
|
|
||||||
|
@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.createObjectsAndFieldsMetadata(
|
||||||
|
dataSourceMetadata.id,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create all standard objects and fields metadata for a given workspace
|
||||||
|
*
|
||||||
|
* @param dataSourceMetadataId
|
||||||
|
* @param workspaceId
|
||||||
|
*/
|
||||||
|
private async createObjectsAndFieldsMetadata(
|
||||||
|
dataSourceMetadataId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) {
|
||||||
|
const createdObjectMetadata = await this.objectMetadataService.createMany(
|
||||||
|
Object.values(standardObjectsMetadata).map((objectMetadata) => ({
|
||||||
|
...objectMetadata,
|
||||||
|
dataSourceId: dataSourceMetadataId,
|
||||||
|
fields: [],
|
||||||
|
workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
isActive: true,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.fieldMetadataService.createMany(
|
||||||
|
createdObjectMetadata.flatMap((objectMetadata: ObjectMetadata) =>
|
||||||
|
standardObjectsMetadata[objectMetadata.nameSingular].fields.map(
|
||||||
|
(field: FieldMetadata) => ({
|
||||||
|
...field,
|
||||||
|
objectId: objectMetadata.id,
|
||||||
|
dataSourceId: dataSourceMetadataId,
|
||||||
|
workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
isActive: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity';
|
||||||
|
|
||||||
|
export const addCompanyTable: TenantMigrationTableAction[] = [
|
||||||
|
{
|
||||||
|
name: 'company',
|
||||||
|
action: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'company',
|
||||||
|
action: 'alter',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'varchar',
|
||||||
|
action: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'domainName',
|
||||||
|
type: 'varchar',
|
||||||
|
action: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'address',
|
||||||
|
type: 'varchar',
|
||||||
|
action: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'employees',
|
||||||
|
type: 'integer',
|
||||||
|
action: 'create',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { addCompanyTable } from './migrations/1697618009-addCompanyTable';
|
||||||
|
|
||||||
|
// TODO: read the folder and return all migrations
|
||||||
|
export const standardMigrations = {
|
||||||
|
'1697618009-addCompanyTable': addCompanyTable,
|
||||||
|
};
|
||||||
@ -5,29 +5,37 @@ import {
|
|||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
export type TenantMigrationColumnChange = {
|
export type TenantMigrationColumnAction = {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
change: 'create' | 'alter';
|
action: 'create';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TenantMigrationTableChange = {
|
export type TenantMigrationTableAction = {
|
||||||
name: string;
|
name: string;
|
||||||
change: 'create' | 'alter';
|
action: 'create' | 'alter';
|
||||||
columns?: TenantMigrationColumnChange[];
|
columns?: TenantMigrationColumnAction[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@Entity('tenant_migrations')
|
@Entity('tenant_migrations')
|
||||||
export class TenantMigration {
|
export class TenantMigration {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@Column({ nullable: true, type: 'jsonb' })
|
@Column({ nullable: true, type: 'jsonb' })
|
||||||
migrations: TenantMigrationTableChange[];
|
migrations: TenantMigrationTableAction[];
|
||||||
|
|
||||||
@Column({ nullable: true, name: 'applied_at' })
|
@Column({ nullable: true })
|
||||||
appliedAt: Date;
|
name: string;
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
@Column({ default: false })
|
||||||
|
isCustom: boolean;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
appliedAt?: Date;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workspaceId: string;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
|
||||||
|
|
||||||
import { TenantMigrationService } from './tenant-migration.service';
|
import { TenantMigrationService } from './tenant-migration.service';
|
||||||
|
import { TenantMigration } from './tenant-migration.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DataSourceModule],
|
imports: [TypeOrmModule.forFeature([TenantMigration], 'metadata')],
|
||||||
exports: [TenantMigrationService],
|
exports: [TenantMigrationService],
|
||||||
providers: [TenantMigrationService],
|
providers: [TenantMigrationService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
|
||||||
|
|
||||||
import { TenantMigrationService } from './tenant-migration.service';
|
import { TenantMigrationService } from './tenant-migration.service';
|
||||||
|
import { TenantMigration } from './tenant-migration.entity';
|
||||||
|
|
||||||
describe('TenantMigrationService', () => {
|
describe('TenantMigrationService', () => {
|
||||||
let service: TenantMigrationService;
|
let service: TenantMigrationService;
|
||||||
@ -12,7 +12,7 @@ describe('TenantMigrationService', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
TenantMigrationService,
|
TenantMigrationService,
|
||||||
{
|
{
|
||||||
provide: DataSourceService,
|
provide: getRepositoryToken(TenantMigration, 'metadata'),
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,17 +1,55 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TenantMigration,
|
TenantMigration,
|
||||||
TenantMigrationTableChange,
|
TenantMigrationTableAction,
|
||||||
} from './tenant-migration.entity';
|
} from './tenant-migration.entity';
|
||||||
|
import { standardMigrations } from './standard-migrations';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TenantMigrationService {
|
export class TenantMigrationService {
|
||||||
constructor(private readonly dataSourceService: DataSourceService) {}
|
constructor(
|
||||||
|
@InjectRepository(TenantMigration, 'metadata')
|
||||||
|
private readonly tenantMigrationRepository: Repository<TenantMigration>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert all standard migrations that have not been inserted yet
|
||||||
|
*
|
||||||
|
* @param workspaceId
|
||||||
|
*/
|
||||||
|
public async insertStandardMigrations(workspaceId: string) {
|
||||||
|
// TODO: we actually don't need to fetch all of them, to improve later so it scales well.
|
||||||
|
const insertedStandardMigrations =
|
||||||
|
await this.tenantMigrationRepository.find({
|
||||||
|
where: { workspaceId, isCustom: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const insertedStandardMigrationsMapByName =
|
||||||
|
insertedStandardMigrations.reduce((acc, migration) => {
|
||||||
|
acc[migration.name] = migration;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const standardMigrationsList = standardMigrations;
|
||||||
|
|
||||||
|
const standardMigrationsListThatNeedToBeInserted = Object.entries(
|
||||||
|
standardMigrationsList,
|
||||||
|
)
|
||||||
|
.filter(([name]) => !insertedStandardMigrationsMapByName[name])
|
||||||
|
.map(([name, migrations]) => ({ name, migrations }));
|
||||||
|
|
||||||
|
await this.tenantMigrationRepository.save(
|
||||||
|
standardMigrationsListThatNeedToBeInserted.map((migration) => ({
|
||||||
|
...migration,
|
||||||
|
workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all pending migrations for a given workspaceId
|
* Get all pending migrations for a given workspaceId
|
||||||
@ -22,19 +60,12 @@ export class TenantMigrationService {
|
|||||||
public async getPendingMigrations(
|
public async getPendingMigrations(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<TenantMigration[]> {
|
): Promise<TenantMigration[]> {
|
||||||
const workspaceDataSource =
|
return this.tenantMigrationRepository.find({
|
||||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
|
||||||
throw new Error('Workspace data source not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const tenantMigrationRepository =
|
|
||||||
workspaceDataSource.getRepository(TenantMigration);
|
|
||||||
|
|
||||||
return tenantMigrationRepository.find({
|
|
||||||
order: { createdAt: 'ASC' },
|
order: { createdAt: 'ASC' },
|
||||||
where: { appliedAt: IsNull() },
|
where: {
|
||||||
|
appliedAt: IsNull(),
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,17 +80,7 @@ export class TenantMigrationService {
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
migration: TenantMigration,
|
migration: TenantMigration,
|
||||||
) {
|
) {
|
||||||
const workspaceDataSource =
|
await this.tenantMigrationRepository.save({
|
||||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
|
||||||
throw new Error('Workspace data source not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const tenantMigrationRepository =
|
|
||||||
workspaceDataSource.getRepository(TenantMigration);
|
|
||||||
|
|
||||||
await tenantMigrationRepository.save({
|
|
||||||
id: migration.id,
|
id: migration.id,
|
||||||
appliedAt: new Date(),
|
appliedAt: new Date(),
|
||||||
});
|
});
|
||||||
@ -71,22 +92,14 @@ export class TenantMigrationService {
|
|||||||
* @param workspaceId
|
* @param workspaceId
|
||||||
* @param migrations
|
* @param migrations
|
||||||
*/
|
*/
|
||||||
public async createMigration(
|
public async createCustomMigration(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
migrations: TenantMigrationTableChange[],
|
migrations: TenantMigrationTableAction[],
|
||||||
) {
|
) {
|
||||||
const workspaceDataSource =
|
await this.tenantMigrationRepository.save({
|
||||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
|
||||||
throw new Error('Workspace data source not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const tenantMigrationRepository =
|
|
||||||
workspaceDataSource.getRepository(TenantMigration);
|
|
||||||
|
|
||||||
await tenantMigrationRepository.save({
|
|
||||||
migrations,
|
migrations,
|
||||||
|
workspaceId,
|
||||||
|
isCustom: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user