import { Injectable } from '@nestjs/common'; import { DataSourceService } from 'src/metadata/data-source/data-source.service'; import { FieldMetadataService } from 'src/metadata/field-metadata/field-metadata.service'; import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service'; import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service'; import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service'; import { standardObjectsPrefillData } from 'src/workspace/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data'; import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity'; import { RelationMetadataService } from 'src/metadata/relation-metadata/relation-metadata.service'; import { standardObjectRelationMetadata } from 'src/workspace/workspace-manager/standard-objects/standard-object-relation-metadata'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { FieldMetadataEntity, FieldMetadataType, } from 'src/metadata/field-metadata/field-metadata.entity'; import { basicFieldsMetadata, standardObjectsMetadata, } from './standard-objects/standard-object-metadata'; @Injectable() export class WorkspaceManagerService { constructor( private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly workspaceMigrationService: WorkspaceMigrationService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly objectMetadataService: ObjectMetadataService, private readonly fieldMetadataService: FieldMetadataService, private readonly dataSourceService: DataSourceService, private readonly relationMetadataService: RelationMetadataService, ) {} /** * Init a workspace by creating a new data source and running all migrations * @param workspaceId * @returns Promise */ public async init(workspaceId: string): Promise { const schemaName = await this.workspaceDataSourceService.createWorkspaceDBSchema( workspaceId, ); const dataSourceMetadata = await this.dataSourceService.createDataSourceMetadata( workspaceId, schemaName, ); await this.workspaceMigrationService.insertStandardMigrations(workspaceId); await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( workspaceId, ); const createdObjectMetadata = await this.createStandardObjectsAndFieldsMetadata( dataSourceMetadata.id, workspaceId, ); await this.prefillWorkspaceWithStandardObjects( dataSourceMetadata, workspaceId, createdObjectMetadata, ); } /** * * Create all standard objects and fields metadata for a given workspace * * @param dataSourceId * @param workspaceId */ public async createStandardObjectsAndFieldsMetadata( dataSourceId: string, workspaceId: string, ): Promise { const createdObjectMetadata = await this.objectMetadataService.createMany( Object.values(standardObjectsMetadata).map( (objectMetadata: ObjectMetadataEntity) => ({ ...objectMetadata, dataSourceId, workspaceId, isCustom: false, isActive: true, fields: [...basicFieldsMetadata, ...objectMetadata.fields].map( (field) => ({ ...field, workspaceId, isCustom: false, isActive: true, }), ), }), ), ); await this.relationMetadataService.createMany( Object.values(standardObjectRelationMetadata).map((relationMetadata) => this.createStandardObjectRelations( workspaceId, createdObjectMetadata, relationMetadata, ), ), ); return createdObjectMetadata; } /** * * @param workspaceId * @param createdObjectMetadata * @param relationMetadata * @returns Partial */ private createStandardObjectRelations( workspaceId: string, createdObjectMetadata: ObjectMetadataEntity[], relationMetadata: any, ) { const createdObjectMetadataByNameSingular = createdObjectMetadata.reduce( (acc, curr) => { acc[curr.nameSingular] = curr; return acc; }, {}, ); const fromObjectMetadata = createdObjectMetadataByNameSingular[ relationMetadata.fromObjectNameSingular ]; const toObjectMetadata = createdObjectMetadataByNameSingular[ relationMetadata.toObjectNameSingular ]; if (!fromObjectMetadata) { throw new Error( `Could not find created object metadata with fromObjectNameSingular: ${relationMetadata.fromObjectNameSingular}`, ); } if (!toObjectMetadata) { throw new Error( `Could not find created object metadata with toObjectNameSingular: ${relationMetadata.toObjectNameSingular}`, ); } const fromFieldMetadata = createdObjectMetadataByNameSingular[ relationMetadata.fromObjectNameSingular ]?.fields.find( (field: FieldMetadataEntity) => field.type === FieldMetadataType.RELATION && field.name === relationMetadata.fromFieldMetadataName, ); const toFieldMetadata = createdObjectMetadataByNameSingular[ relationMetadata.toObjectNameSingular ]?.fields.find( (field: FieldMetadataEntity) => field.type === FieldMetadataType.RELATION && field.name === relationMetadata.toFieldMetadataName, ); if (!fromFieldMetadata) { throw new Error( `Could not find created field metadata with fromFieldMetadataName: ${relationMetadata.fromFieldMetadataName} for object: ${relationMetadata.fromObjectNameSingular}`, ); } if (!toFieldMetadata) { throw new Error( `Could not find created field metadata with toFieldMetadataName: ${relationMetadata.toFieldMetadataName} for object: ${relationMetadata.toObjectNameSingular}`, ); } return { fromObjectMetadataId: fromObjectMetadata.id, toObjectMetadataId: toObjectMetadata.id, workspaceId, relationType: relationMetadata.type, fromFieldMetadataId: fromFieldMetadata.id, toFieldMetadataId: toFieldMetadata.id, }; } /** * * Reset all standard objects and fields metadata for a given workspace * * @param dataSourceId * @param workspaceId */ public async resetStandardObjectsAndFieldsMetadata( dataSourceId: string, workspaceId: string, ) { await this.objectMetadataService.deleteMany({ workspaceId: { eq: workspaceId }, }); await this.createStandardObjectsAndFieldsMetadata( dataSourceId, 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: DataSourceEntity, workspaceId: string, createdObjectMetadata: ObjectMetadataEntity[], ) { const workspaceDataSource = await this.workspaceDataSourceService.connectToWorkspaceDataSource( workspaceId, ); if (!workspaceDataSource) { throw new Error('Could not connect to workspace data source'); } standardObjectsPrefillData( workspaceDataSource, dataSourceMetadata.schema, createdObjectMetadata, ); } /** * * Delete a workspace by deleting all metadata and the schema * * @param workspaceId */ public async delete(workspaceId: string): Promise { // Delete data from metadata tables await this.fieldMetadataService.deleteFieldsMetadata(workspaceId); await this.objectMetadataService.deleteObjectsMetadata(workspaceId); await this.workspaceMigrationService.delete(workspaceId); await this.dataSourceService.delete(workspaceId); // Delete schema await this.workspaceDataSourceService.deleteWorkspaceDBSchema(workspaceId); } }