Files
twenty/server/src/workspace/workspace-manager/workspace-manager.service.ts
2023-11-17 22:59:10 +01:00

266 lines
8.3 KiB
TypeScript

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<void>
*/
public async init(workspaceId: string): Promise<void> {
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<ObjectMetadataEntity[]> {
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<RelationMetadataEntity>
*/
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<void> {
// 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);
}
}