In this PR we are 1. cleaning typeORM service by removing connectToDataSource method 2. using workspaceDataSource instead of mainDataSource when possible, and replacing raw SQL with workspaceRepository methods to use
387 lines
13 KiB
TypeScript
387 lines
13 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
|
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
|
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 { 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<FieldMetadataEntity>,
|
|
@InjectRepository(RelationMetadataEntity, 'metadata')
|
|
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
|
|
@InjectRepository(UserWorkspace, 'core')
|
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
|
private readonly roleService: RoleService,
|
|
private readonly userRoleService: UserRoleService,
|
|
private readonly featureFlagService: FeatureFlagService,
|
|
@InjectRepository(Workspace, 'core')
|
|
private readonly workspaceRepository: Repository<Workspace>,
|
|
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
|
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
|
@InjectRepository(RoleEntity, 'metadata')
|
|
private readonly roleRepository: Repository<RoleEntity>,
|
|
) {}
|
|
|
|
/**
|
|
* Init a workspace by creating a new data source and running all migrations
|
|
* @param workspaceId
|
|
* @returns Promise<void>
|
|
*/
|
|
public async init({
|
|
workspaceId,
|
|
userId,
|
|
}: {
|
|
workspaceId: string;
|
|
userId: string;
|
|
}): Promise<void> {
|
|
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,
|
|
);
|
|
|
|
const featureFlags =
|
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
|
|
|
await this.workspaceSyncMetadataService.synchronize({
|
|
workspaceId,
|
|
dataSourceId: dataSourceMetadata.id,
|
|
featureFlags,
|
|
});
|
|
|
|
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<void>
|
|
*/
|
|
public async initDemo(workspaceId: string): Promise<void> {
|
|
const schemaName =
|
|
await this.workspaceDataSourceService.createWorkspaceDBSchema(
|
|
workspaceId,
|
|
);
|
|
|
|
const dataSourceMetadata =
|
|
await this.dataSourceService.createDataSourceMetadata(
|
|
workspaceId,
|
|
schemaName,
|
|
);
|
|
|
|
const featureFlags =
|
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
|
|
|
await this.workspaceSyncMetadataService.synchronize({
|
|
workspaceId,
|
|
dataSourceId: dataSourceMetadata.id,
|
|
featureFlags,
|
|
});
|
|
|
|
await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId);
|
|
}
|
|
|
|
public async initDev(workspaceId: string): Promise<void> {
|
|
const schemaName =
|
|
await this.workspaceDataSourceService.createWorkspaceDBSchema(
|
|
workspaceId,
|
|
);
|
|
|
|
const dataSourceMetadata =
|
|
await this.dataSourceService.createDataSourceMetadata(
|
|
workspaceId,
|
|
schemaName,
|
|
);
|
|
|
|
const featureFlags =
|
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
|
|
|
await this.workspaceSyncMetadataService.synchronize({
|
|
workspaceId: workspaceId,
|
|
dataSourceId: dataSourceMetadata.id,
|
|
featureFlags,
|
|
});
|
|
|
|
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 mainDataSource =
|
|
await this.workspaceDataSourceService.connectToMainDataSource();
|
|
|
|
if (!mainDataSource) {
|
|
throw new Error('Could not connect to main data source');
|
|
}
|
|
|
|
const createdObjectMetadata =
|
|
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
|
|
|
await standardObjectsPrefillData(
|
|
mainDataSource,
|
|
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 mainDataSource =
|
|
await this.workspaceDataSourceService.connectToMainDataSource();
|
|
|
|
if (!mainDataSource) {
|
|
throw new Error('Could not connect to main data source');
|
|
}
|
|
|
|
const createdObjectMetadata =
|
|
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
|
|
|
await seedWorkspaceWithDemoData(
|
|
mainDataSource,
|
|
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<void> {
|
|
//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,
|
|
});
|
|
|
|
const memberRole = await this.roleService.createMemberRole({
|
|
workspaceId,
|
|
});
|
|
|
|
await this.workspaceRepository.update(workspaceId, {
|
|
defaultRoleId: memberRole.id,
|
|
});
|
|
}
|
|
|
|
private async initPermissionsDev(workspaceId: string) {
|
|
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,
|
|
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
|
});
|
|
|
|
if (memberUserWorkspaceId) {
|
|
await this.userRoleService.assignRoleToUserWorkspace({
|
|
workspaceId,
|
|
userWorkspaceId: memberUserWorkspaceId,
|
|
roleId: memberRole.id,
|
|
});
|
|
}
|
|
}
|
|
}
|