import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { DEV_SEED_USER_WORKSPACE_IDS } from 'src/database/typeorm-seeds/core/user-workspaces'; import { SEED_ACME_WORKSPACE_ID, SEED_APPLE_WORKSPACE_ID, } from 'src/database/typeorm-seeds/core/workspaces'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { PETS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/pets-data-seeds'; import { SURVEY_RESULTS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/survey-results-data-seeds'; import { PETS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/pets-metadata-seeds'; import { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/survey-results-metadata-seeds'; import { SeederService } from 'src/engine/seeder/seeder.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { seedWorkspaceWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-with-demo-data'; import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data'; import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; @Injectable() export class WorkspaceManagerService { private readonly logger = new Logger(WorkspaceManagerService.name); constructor( private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly workspaceMigrationService: WorkspaceMigrationService, private readonly objectMetadataService: ObjectMetadataService, private readonly seederService: SeederService, private readonly dataSourceService: DataSourceService, private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, private readonly permissionsService: PermissionsService, @InjectRepository(FieldMetadataEntity, 'metadata') private readonly fieldMetadataRepository: Repository, @InjectRepository(RelationMetadataEntity, 'metadata') private readonly relationMetadataRepository: Repository, @InjectRepository(UserWorkspace, 'core') private readonly userWorkspaceRepository: Repository, private readonly roleService: RoleService, private readonly userRoleService: UserRoleService, private readonly featureFlagService: FeatureFlagService, @InjectRepository(Workspace, 'core') private readonly workspaceRepository: Repository, @InjectRepository(UserWorkspaceRoleEntity, 'metadata') private readonly userWorkspaceRoleRepository: Repository, @InjectRepository(RoleEntity, 'metadata') private readonly roleRepository: Repository, ) {} /** * Init a workspace by creating a new data source and running all migrations * @param workspaceId * @returns Promise */ public async init({ workspaceId, userId, }: { workspaceId: string; userId: string; }): Promise { const schemaCreationStart = performance.now(); const schemaName = await this.workspaceDataSourceService.createWorkspaceDBSchema( workspaceId, ); const schemaCreationEnd = performance.now(); this.logger.log( `Schema creation took ${schemaCreationEnd - schemaCreationStart}ms`, ); const dataSourceMetadataCreationStart = performance.now(); const dataSourceMetadata = await this.dataSourceService.createDataSourceMetadata( workspaceId, schemaName, ); await this.workspaceSyncMetadataService.synchronize({ workspaceId, dataSourceId: dataSourceMetadata.id, }); const dataSourceMetadataCreationEnd = performance.now(); this.logger.log( `Metadata creation took ${dataSourceMetadataCreationEnd - dataSourceMetadataCreationStart}ms`, ); const permissionsEnabledStart = performance.now(); await this.initPermissions({ workspaceId, userId }); const permissionsEnabledEnd = performance.now(); this.logger.log( `Permissions enabled took ${permissionsEnabledEnd - permissionsEnabledStart}ms`, ); const prefillStandardObjectsStart = performance.now(); await this.prefillWorkspaceWithStandardObjects( dataSourceMetadata, workspaceId, ); const prefillStandardObjectsEnd = performance.now(); this.logger.log( `Prefill standard objects took ${prefillStandardObjectsEnd - prefillStandardObjectsStart}ms`, ); } /** * InitDemo a workspace by creating a new data source and running all migrations * @param workspaceId * @returns Promise */ public async initDemo(workspaceId: string): Promise { const schemaName = await this.workspaceDataSourceService.createWorkspaceDBSchema( workspaceId, ); const dataSourceMetadata = await this.dataSourceService.createDataSourceMetadata( workspaceId, schemaName, ); await this.workspaceSyncMetadataService.synchronize({ workspaceId, dataSourceId: dataSourceMetadata.id, }); await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId); } public async initDev(workspaceId: string): Promise { const schemaName = await this.workspaceDataSourceService.createWorkspaceDBSchema( workspaceId, ); const dataSourceMetadata = await this.dataSourceService.createDataSourceMetadata( workspaceId, schemaName, ); await this.workspaceSyncMetadataService.synchronize({ workspaceId: workspaceId, dataSourceId: dataSourceMetadata.id, }); await this.initPermissionsDev(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, ) { const workspaceDataSource = await this.workspaceDataSourceService.connectToWorkspaceDataSource( workspaceId, ); if (!workspaceDataSource) { throw new Error('Could not connect to workspace data source'); } const createdObjectMetadata = await this.objectMetadataService.findManyWithinWorkspace(workspaceId); await standardObjectsPrefillData( workspaceDataSource, dataSourceMetadata.schema, createdObjectMetadata, ); } /** * * We are prefilling a few demo objects with data to make it easier for the user to get started. * * @param dataSourceMetadata * @param workspaceId */ private async prefillWorkspaceWithDemoObjects( dataSourceMetadata: DataSourceEntity, workspaceId: string, ) { const workspaceDataSource = await this.workspaceDataSourceService.connectToWorkspaceDataSource( workspaceId, ); if (!workspaceDataSource) { throw new Error('Could not connect to workspace data source'); } const createdObjectMetadata = await this.objectMetadataService.findManyWithinWorkspace(workspaceId); await seedWorkspaceWithDemoData( workspaceDataSource, dataSourceMetadata.schema, createdObjectMetadata, ); await this.seederService.seedCustomObjects( dataSourceMetadata.id, workspaceId, PETS_METADATA_SEEDS, PETS_DATA_SEEDS, ); await this.seederService.seedCustomObjects( dataSourceMetadata.id, workspaceId, SURVEY_RESULTS_METADATA_SEEDS, SURVEY_RESULTS_DATA_SEEDS, ); } /** * * Delete a workspace by deleting all metadata and the schema * * @param workspaceId */ public async delete(workspaceId: string): Promise { //TODO: delete all logs when #611 closed this.logger.log(`Deleting workspace ${workspaceId} ...`); // Delete data from metadata tables await this.relationMetadataRepository.delete({ workspaceId, }); this.logger.log(`workspace ${workspaceId} relation metadata deleted`); await this.fieldMetadataRepository.delete({ workspaceId, }); this.logger.log(`workspace ${workspaceId} field metadata deleted`); await this.userWorkspaceRoleRepository.delete({ workspaceId, }); this.logger.log(`workspace ${workspaceId} user workspace role deleted`); await this.roleRepository.delete({ workspaceId, }); this.logger.log(`workspace ${workspaceId} role deleted`); await this.objectMetadataService.deleteObjectsMetadata(workspaceId); this.logger.log(`workspace ${workspaceId} object metadata deleted`); await this.workspaceMigrationService.deleteAllWithinWorkspace(workspaceId); this.logger.log(`workspace ${workspaceId} migration deleted`); await this.dataSourceService.delete(workspaceId); this.logger.log(`workspace ${workspaceId} data source deleted`); // Delete schema await this.workspaceDataSourceService.deleteWorkspaceDBSchema(workspaceId); this.logger.log(`workspace ${workspaceId} schema deleted`); } private async initPermissions({ workspaceId, userId, }: { workspaceId: string; userId: string; }) { const adminRole = await this.roleService.createAdminRole({ workspaceId, }); const userWorkspace = await this.userWorkspaceRepository.findOneOrFail({ where: { workspaceId, userId, }, }); await this.userRoleService.assignRoleToUserWorkspace({ workspaceId, userWorkspaceId: userWorkspace.id, roleId: adminRole.id, }); await this.roleService.createMemberRole({ workspaceId, }); // Temporary - after permissions are rolled-out we will set member role as the default role await this.workspaceRepository.update(workspaceId, { defaultRoleId: adminRole.id, }); } private async initPermissionsDev(workspaceId: string) { await this.featureFlagService.enableFeatureFlags( [FeatureFlagKey.IsPermissionsEnabled], workspaceId, ); const adminRole = await this.roleService.createAdminRole({ workspaceId, }); let adminUserWorkspaceId: string | undefined; let memberUserWorkspaceId: string | undefined; if (workspaceId === SEED_APPLE_WORKSPACE_ID) { adminUserWorkspaceId = DEV_SEED_USER_WORKSPACE_IDS.TIM; memberUserWorkspaceId = DEV_SEED_USER_WORKSPACE_IDS.JONY; // Create guest role only in this workspace const guestRole = await this.roleService.createGuestRole({ workspaceId, }); await this.userRoleService.assignRoleToUserWorkspace({ workspaceId, userWorkspaceId: DEV_SEED_USER_WORKSPACE_IDS.PHIL, roleId: guestRole.id, }); } else if (workspaceId === SEED_ACME_WORKSPACE_ID) { adminUserWorkspaceId = DEV_SEED_USER_WORKSPACE_IDS.TIM_ACME; } if (adminUserWorkspaceId) { await this.userRoleService.assignRoleToUserWorkspace({ workspaceId, userWorkspaceId: adminUserWorkspaceId, roleId: adminRole.id, }); } const memberRole = await this.roleService.createMemberRole({ workspaceId, }); await this.workspaceRepository.update(workspaceId, { defaultRoleId: memberRole.id, }); if (memberUserWorkspaceId) { await this.userRoleService.assignRoleToUserWorkspace({ workspaceId, userWorkspaceId: memberUserWorkspaceId, roleId: memberRole.id, }); } } }