d532f22fbb
* fix: remove old metadata seed files * feat: wip standard to core relation * fix: lint * fix: merge * fix: remove debug files * feat: add feature flag for core object metadata * fix: remove debug * feat: always disable the standard core relation * fix: missing feature flag * fix: remove debug * fix: feature flag doesn't seems to disable relation * fix: delete .vscode folder, change this in another PR * Update packages/twenty-server/src/workspace/workspace-sync-metadata/reflective-metadata.factory.ts Co-authored-by: Weiko <corentin@twenty.com> * Update packages/twenty-server/src/workspace/workspace-sync-metadata/reflective-metadata.factory.ts Co-authored-by: Weiko <corentin@twenty.com> * Update packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync.metadata.service.ts Co-authored-by: Weiko <corentin@twenty.com> * fix: remove optional fields from metadata entities * fix: renamed variable * fix: put back CursorScalarType * fix: delete test command * fix: remove unused workspace standard migration command * fix: drop core object metadata declaration * fix: rename variable * fix: drop creation of core datasource * fix: remove feature flag * fix: drop support of standard to core relations * feat: add user email field on workspace-member standard object * fix: update seed accordingly * fix: missing remove command file * fix: datasource label should remain nullable * fix: better asserts * Remove unused code * Remove unused code --------- Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: Charles Bochet <charles@twenty.com>
314 lines
9.4 KiB
TypeScript
314 lines
9.4 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
|
|
import {
|
|
QueryRunner,
|
|
Table,
|
|
TableColumn,
|
|
TableForeignKey,
|
|
TableUnique,
|
|
} from 'typeorm';
|
|
|
|
import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service';
|
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
|
import {
|
|
WorkspaceMigrationTableAction,
|
|
WorkspaceMigrationColumnAction,
|
|
WorkspaceMigrationColumnActionType,
|
|
WorkspaceMigrationColumnCreate,
|
|
WorkspaceMigrationColumnRelation,
|
|
WorkspaceMigrationColumnAlter,
|
|
} from 'src/metadata/workspace-migration/workspace-migration.entity';
|
|
import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-version/workspace-cache-version.service';
|
|
import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration-runner/services/workspace-migration-enum.service';
|
|
|
|
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
|
|
|
|
@Injectable()
|
|
export class WorkspaceMigrationRunnerService {
|
|
constructor(
|
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
|
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
|
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
|
|
private readonly workspaceMigrationEnumService: WorkspaceMigrationEnumService,
|
|
) {}
|
|
|
|
/**
|
|
* Executes pending migrations for a given workspace
|
|
*
|
|
* @param workspaceId string
|
|
* @returns Promise<WorkspaceMigrationTableAction[]>
|
|
*/
|
|
public async executeMigrationFromPendingMigrations(
|
|
workspaceId: string,
|
|
): Promise<WorkspaceMigrationTableAction[]> {
|
|
const workspaceDataSource =
|
|
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
|
workspaceId,
|
|
);
|
|
|
|
if (!workspaceDataSource) {
|
|
throw new Error('Workspace data source not found');
|
|
}
|
|
|
|
const pendingMigrations =
|
|
await this.workspaceMigrationService.getPendingMigrations(workspaceId);
|
|
|
|
if (pendingMigrations.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const flattenedPendingMigrations: WorkspaceMigrationTableAction[] =
|
|
pendingMigrations.reduce((acc, pendingMigration) => {
|
|
return [...acc, ...pendingMigration.migrations];
|
|
}, []);
|
|
|
|
const queryRunner = workspaceDataSource?.createQueryRunner();
|
|
|
|
// 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, migration);
|
|
}
|
|
|
|
// Update appliedAt date for each migration
|
|
// TODO: Should be done after the migration is successful
|
|
for (const pendingMigration of pendingMigrations) {
|
|
await this.workspaceMigrationService.setAppliedAtForMigration(
|
|
workspaceId,
|
|
pendingMigration,
|
|
);
|
|
}
|
|
|
|
await queryRunner.release();
|
|
|
|
// Increment workspace cache version
|
|
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
|
|
|
|
return flattenedPendingMigrations;
|
|
}
|
|
|
|
/**
|
|
* Handles table changes for a given migration
|
|
*
|
|
* @param queryRunner QueryRunner
|
|
* @param schemaName string
|
|
* @param tableMigration WorkspaceMigrationTableChange
|
|
*/
|
|
private async handleTableChanges(
|
|
queryRunner: QueryRunner,
|
|
tableMigration: WorkspaceMigrationTableAction,
|
|
) {
|
|
switch (tableMigration.action) {
|
|
case 'create':
|
|
await this.createTable(
|
|
queryRunner,
|
|
tableMigration.schemaName,
|
|
tableMigration.name,
|
|
);
|
|
break;
|
|
case 'alter':
|
|
await this.handleColumnChanges(
|
|
queryRunner,
|
|
tableMigration.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 WorkspaceMigrationColumnAction[]
|
|
* @returns
|
|
*/
|
|
private async handleColumnChanges(
|
|
queryRunner: QueryRunner,
|
|
schemaName: string,
|
|
tableName: string,
|
|
columnMigrations?: WorkspaceMigrationColumnAction[],
|
|
) {
|
|
if (!columnMigrations || columnMigrations.length === 0) {
|
|
return;
|
|
}
|
|
|
|
for (const columnMigration of columnMigrations) {
|
|
switch (columnMigration.action) {
|
|
case WorkspaceMigrationColumnActionType.CREATE:
|
|
await this.createColumn(
|
|
queryRunner,
|
|
schemaName,
|
|
tableName,
|
|
columnMigration,
|
|
);
|
|
break;
|
|
case WorkspaceMigrationColumnActionType.ALTER:
|
|
await this.alterColumn(
|
|
queryRunner,
|
|
schemaName,
|
|
tableName,
|
|
columnMigration,
|
|
);
|
|
break;
|
|
case WorkspaceMigrationColumnActionType.RELATION:
|
|
await this.createRelation(
|
|
queryRunner,
|
|
schemaName,
|
|
tableName,
|
|
columnMigration,
|
|
);
|
|
break;
|
|
case WorkspaceMigrationColumnActionType.DROP:
|
|
await queryRunner.dropColumn(
|
|
`${schemaName}.${tableName}`,
|
|
columnMigration.columnName,
|
|
);
|
|
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 WorkspaceMigrationColumnAction
|
|
*/
|
|
private async createColumn(
|
|
queryRunner: QueryRunner,
|
|
schemaName: string,
|
|
tableName: string,
|
|
migrationColumn: WorkspaceMigrationColumnCreate,
|
|
) {
|
|
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,
|
|
default: migrationColumn.defaultValue,
|
|
enum: migrationColumn.enum?.filter(
|
|
(value): value is string => typeof value === 'string',
|
|
),
|
|
isArray: migrationColumn.isArray,
|
|
isNullable: migrationColumn.isNullable,
|
|
}),
|
|
);
|
|
}
|
|
|
|
private async alterColumn(
|
|
queryRunner: QueryRunner,
|
|
schemaName: string,
|
|
tableName: string,
|
|
migrationColumn: WorkspaceMigrationColumnAlter,
|
|
) {
|
|
const enumValues = migrationColumn.alteredColumnDefinition.enum;
|
|
|
|
// TODO: Maybe we can do something better if we can recreate the old `TableColumn` object
|
|
if (enumValues) {
|
|
// This is returning the old enum values to avoid TypeORM droping the enum type
|
|
await this.workspaceMigrationEnumService.alterEnum(
|
|
queryRunner,
|
|
schemaName,
|
|
tableName,
|
|
migrationColumn,
|
|
);
|
|
}
|
|
|
|
await queryRunner.changeColumn(
|
|
`${schemaName}.${tableName}`,
|
|
new TableColumn({
|
|
name: migrationColumn.currentColumnDefinition.columnName,
|
|
type: migrationColumn.currentColumnDefinition.columnType,
|
|
default: migrationColumn.currentColumnDefinition.defaultValue,
|
|
enum: migrationColumn.currentColumnDefinition.enum?.filter(
|
|
(value): value is string => typeof value === 'string',
|
|
),
|
|
isArray: migrationColumn.currentColumnDefinition.isArray,
|
|
isNullable: migrationColumn.currentColumnDefinition.isNullable,
|
|
}),
|
|
new TableColumn({
|
|
name: migrationColumn.alteredColumnDefinition.columnName,
|
|
type: migrationColumn.alteredColumnDefinition.columnType,
|
|
default: migrationColumn.alteredColumnDefinition.defaultValue,
|
|
enum: migrationColumn.currentColumnDefinition.enum?.filter(
|
|
(value): value is string => typeof value === 'string',
|
|
),
|
|
isArray: migrationColumn.alteredColumnDefinition.isArray,
|
|
isNullable: migrationColumn.alteredColumnDefinition.isNullable,
|
|
}),
|
|
);
|
|
}
|
|
|
|
private async createRelation(
|
|
queryRunner: QueryRunner,
|
|
schemaName: string,
|
|
tableName: string,
|
|
migrationColumn: WorkspaceMigrationColumnRelation,
|
|
) {
|
|
await queryRunner.createForeignKey(
|
|
`${schemaName}.${tableName}`,
|
|
new TableForeignKey({
|
|
columnNames: [migrationColumn.columnName],
|
|
referencedColumnNames: [migrationColumn.referencedTableColumnName],
|
|
referencedSchema: migrationColumn.referencedSchema,
|
|
referencedTableName: migrationColumn.referencedTableName,
|
|
onDelete: 'CASCADE',
|
|
}),
|
|
);
|
|
|
|
// Create unique constraint if for one to one relation
|
|
if (migrationColumn.isUnique) {
|
|
await queryRunner.createUniqueConstraint(
|
|
`${schemaName}.${tableName}`,
|
|
new TableUnique({
|
|
name: `UNIQUE_${tableName}_${migrationColumn.columnName}`,
|
|
columnNames: [migrationColumn.columnName],
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|