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:
@ -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,
|
||||
} from 'typeorm';
|
||||
|
||||
export type TenantMigrationColumnChange = {
|
||||
export type TenantMigrationColumnAction = {
|
||||
name: string;
|
||||
type: string;
|
||||
change: 'create' | 'alter';
|
||||
action: 'create';
|
||||
};
|
||||
|
||||
export type TenantMigrationTableChange = {
|
||||
export type TenantMigrationTableAction = {
|
||||
name: string;
|
||||
change: 'create' | 'alter';
|
||||
columns?: TenantMigrationColumnChange[];
|
||||
action: 'create' | 'alter';
|
||||
columns?: TenantMigrationColumnAction[];
|
||||
};
|
||||
|
||||
@Entity('tenant_migrations')
|
||||
export class TenantMigration {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
migrations: TenantMigrationTableChange[];
|
||||
migrations: TenantMigrationTableAction[];
|
||||
|
||||
@Column({ nullable: true, name: 'applied_at' })
|
||||
appliedAt: Date;
|
||||
@Column({ nullable: true })
|
||||
name: string;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
@Column({ default: false })
|
||||
isCustom: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
appliedAt?: Date;
|
||||
|
||||
@Column()
|
||||
workspaceId: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { TenantMigrationService } from './tenant-migration.service';
|
||||
import { TenantMigration } from './tenant-migration.entity';
|
||||
|
||||
@Module({
|
||||
imports: [DataSourceModule],
|
||||
imports: [TypeOrmModule.forFeature([TenantMigration], 'metadata')],
|
||||
exports: [TenantMigrationService],
|
||||
providers: [TenantMigrationService],
|
||||
})
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { TenantMigrationService } from './tenant-migration.service';
|
||||
import { TenantMigration } from './tenant-migration.entity';
|
||||
|
||||
describe('TenantMigrationService', () => {
|
||||
let service: TenantMigrationService;
|
||||
@ -12,7 +12,7 @@ describe('TenantMigrationService', () => {
|
||||
providers: [
|
||||
TenantMigrationService,
|
||||
{
|
||||
provide: DataSourceService,
|
||||
provide: getRepositoryToken(TenantMigration, 'metadata'),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
|
||||
@ -1,17 +1,55 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { IsNull } from 'typeorm';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
TenantMigration,
|
||||
TenantMigrationTableChange,
|
||||
TenantMigrationTableAction,
|
||||
} from './tenant-migration.entity';
|
||||
import { standardMigrations } from './standard-migrations';
|
||||
|
||||
@Injectable()
|
||||
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
|
||||
@ -22,19 +60,12 @@ export class TenantMigrationService {
|
||||
public async getPendingMigrations(
|
||||
workspaceId: string,
|
||||
): Promise<TenantMigration[]> {
|
||||
const workspaceDataSource =
|
||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Workspace data source not found');
|
||||
}
|
||||
|
||||
const tenantMigrationRepository =
|
||||
workspaceDataSource.getRepository(TenantMigration);
|
||||
|
||||
return tenantMigrationRepository.find({
|
||||
return this.tenantMigrationRepository.find({
|
||||
order: { createdAt: 'ASC' },
|
||||
where: { appliedAt: IsNull() },
|
||||
where: {
|
||||
appliedAt: IsNull(),
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,17 +80,7 @@ export class TenantMigrationService {
|
||||
workspaceId: string,
|
||||
migration: TenantMigration,
|
||||
) {
|
||||
const workspaceDataSource =
|
||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Workspace data source not found');
|
||||
}
|
||||
|
||||
const tenantMigrationRepository =
|
||||
workspaceDataSource.getRepository(TenantMigration);
|
||||
|
||||
await tenantMigrationRepository.save({
|
||||
await this.tenantMigrationRepository.save({
|
||||
id: migration.id,
|
||||
appliedAt: new Date(),
|
||||
});
|
||||
@ -71,22 +92,14 @@ export class TenantMigrationService {
|
||||
* @param workspaceId
|
||||
* @param migrations
|
||||
*/
|
||||
public async createMigration(
|
||||
public async createCustomMigration(
|
||||
workspaceId: string,
|
||||
migrations: TenantMigrationTableChange[],
|
||||
migrations: TenantMigrationTableAction[],
|
||||
) {
|
||||
const workspaceDataSource =
|
||||
await this.dataSourceService.connectToWorkspaceDataSource(workspaceId);
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Workspace data source not found');
|
||||
}
|
||||
|
||||
const tenantMigrationRepository =
|
||||
workspaceDataSource.getRepository(TenantMigration);
|
||||
|
||||
await tenantMigrationRepository.save({
|
||||
await this.tenantMigrationRepository.save({
|
||||
migrations,
|
||||
workspaceId,
|
||||
isCustom: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user