Files
twenty/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts

383 lines
13 KiB
TypeScript

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<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,
);
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<void>
*/
public async initDemo(workspaceId: string): Promise<void> {
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<void> {
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<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,
});
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,
});
}
}
}