Add ServerlessFunction to migration v2 (#14698)
This commit is contained in:
+369
@@ -0,0 +1,369 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { basename, dirname, join } from 'path';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils';
|
||||
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-id.input';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { fromCreateServerlessFunctionInputToFlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/utils/from-create-serverless-function-input-to-flat-serverless-function.util';
|
||||
import { fromUpdateServerlessFunctionInputToFlatServerlessFunctionToUpdateOrThrow } from 'src/engine/metadata-modules/serverless-function/utils/from-update-serverless-function-input-to-flat-serverless-function-to-update-or-throw.util';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionV2Service {
|
||||
constructor(
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly serverlessService: ServerlessService,
|
||||
) {}
|
||||
|
||||
async createOne(
|
||||
serverlessFunctionInput: CreateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const flatEntityMaps =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingFlatServerlessFunctionMaps =
|
||||
flatEntityMaps.flatServerlessFunctionMaps;
|
||||
|
||||
const flatServerlessFunctionToCreate =
|
||||
fromCreateServerlessFunctionInputToFlatServerlessFunction({
|
||||
createServerlessFunctionInput: serverlessFunctionInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const toFlatServerlessFunctionMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: flatServerlessFunctionToCreate,
|
||||
flatEntityMaps: existingFlatServerlessFunctionMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
workspaceId,
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatServerlessFunctionMaps: {
|
||||
from: existingFlatServerlessFunctionMaps,
|
||||
to: toFlatServerlessFunctionMaps,
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating serverless function',
|
||||
);
|
||||
}
|
||||
|
||||
const updatedFlatEntityMaps =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const createdServerlessFunction =
|
||||
updatedFlatEntityMaps.flatServerlessFunctionMaps.byId[
|
||||
flatServerlessFunctionToCreate.id
|
||||
];
|
||||
|
||||
if (!isDefined(createdServerlessFunction)) {
|
||||
throw new ServerlessFunctionException(
|
||||
'Created serverless function not found in recomputed cache',
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const draftFileFolder = getServerlessFolder({
|
||||
serverlessFunction: createdServerlessFunction,
|
||||
version: 'draft',
|
||||
});
|
||||
|
||||
for (const file of await getBaseTypescriptProjectFiles) {
|
||||
await this.fileStorageService.write({
|
||||
file: file.content,
|
||||
name: file.name,
|
||||
mimeType: undefined,
|
||||
folder: join(draftFileFolder, file.path),
|
||||
});
|
||||
}
|
||||
|
||||
return createdServerlessFunction;
|
||||
}
|
||||
|
||||
async updateOne(
|
||||
serverlessFunctionInput: UpdateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const flatEntityMaps =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingFlatServerlessFunctionMaps =
|
||||
flatEntityMaps.flatServerlessFunctionMaps;
|
||||
|
||||
const optimisticallyUpdatedFlatServerlessFunction =
|
||||
fromUpdateServerlessFunctionInputToFlatServerlessFunctionToUpdateOrThrow({
|
||||
flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps,
|
||||
updateServerlessFunctionInput: serverlessFunctionInput,
|
||||
});
|
||||
|
||||
const fromFlatServerlessFunctionMaps = getSubFlatEntityMapsOrThrow({
|
||||
flatEntityIds: [optimisticallyUpdatedFlatServerlessFunction.id],
|
||||
flatEntityMaps: existingFlatServerlessFunctionMaps,
|
||||
});
|
||||
const toFlatServerlessFunctionMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: optimisticallyUpdatedFlatServerlessFunction,
|
||||
flatEntityMaps: fromFlatServerlessFunctionMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
workspaceId,
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatServerlessFunctionMaps: {
|
||||
from: existingFlatServerlessFunctionMaps,
|
||||
to: toFlatServerlessFunctionMaps,
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating serverless function',
|
||||
);
|
||||
}
|
||||
|
||||
const updatedFlatEntityMaps =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const updatedFlatServerlessFunction =
|
||||
updatedFlatEntityMaps.flatServerlessFunctionMaps.byId[
|
||||
optimisticallyUpdatedFlatServerlessFunction.id
|
||||
];
|
||||
|
||||
if (!isDefined(updatedFlatServerlessFunction)) {
|
||||
throw new ServerlessFunctionException(
|
||||
'Updated serverless function not found in recomputed cache',
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const fileFolder = getServerlessFolder({
|
||||
serverlessFunction: updatedFlatServerlessFunction,
|
||||
version: 'draft',
|
||||
});
|
||||
|
||||
for (const key of Object.keys(serverlessFunctionInput.code)) {
|
||||
await this.fileStorageService.write({
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
file: serverlessFunctionInput.code[key],
|
||||
name: basename(key),
|
||||
mimeType: undefined,
|
||||
folder: join(fileFolder, dirname(key)),
|
||||
});
|
||||
}
|
||||
|
||||
return updatedFlatServerlessFunction;
|
||||
}
|
||||
|
||||
async deleteOne({
|
||||
deleteServerlessFunctionInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
deleteServerlessFunctionInput: ServerlessFunctionIdInput;
|
||||
workspaceId: string;
|
||||
}): Promise<FlatServerlessFunction> {
|
||||
const { flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingFlatServerlessFunction =
|
||||
existingFlatServerlessFunctionMaps.byId[deleteServerlessFunctionInput.id];
|
||||
|
||||
if (!isDefined(existingFlatServerlessFunction)) {
|
||||
throw new ServerlessFunctionException(
|
||||
'Serverless function to delete not found',
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const optimisticallyUpdatedFlatServerlessFunctionWithDeletedAt = {
|
||||
...existingFlatServerlessFunction,
|
||||
deletedAt: new Date(),
|
||||
};
|
||||
|
||||
const toFlatServerlessFunctionMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: optimisticallyUpdatedFlatServerlessFunctionWithDeletedAt,
|
||||
flatEntityMaps: existingFlatServerlessFunctionMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatServerlessFunctionMaps: {
|
||||
from: existingFlatServerlessFunctionMaps,
|
||||
to: toFlatServerlessFunctionMaps,
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting serverless function',
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
flatServerlessFunctionMaps: recomputedExistingFlatServerlessFunctionMaps,
|
||||
} =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
this.serverlessService.delete(
|
||||
existingFlatServerlessFunction as ServerlessFunctionEntity,
|
||||
);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: optimisticallyUpdatedFlatServerlessFunctionWithDeletedAt.id,
|
||||
flatEntityMaps: recomputedExistingFlatServerlessFunctionMaps,
|
||||
});
|
||||
}
|
||||
|
||||
async destroyOne({
|
||||
destroyServerlessFunctionInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
destroyServerlessFunctionInput: ServerlessFunctionIdInput;
|
||||
workspaceId: string;
|
||||
}): Promise<FlatServerlessFunction> {
|
||||
const { flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatEntities: ['flatServerlessFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingFlatServerlessFunction =
|
||||
existingFlatServerlessFunctionMaps.byId[
|
||||
destroyServerlessFunctionInput.id
|
||||
];
|
||||
|
||||
if (!isDefined(existingFlatServerlessFunction)) {
|
||||
throw new ServerlessFunctionException(
|
||||
'Serverless function to destroy not found',
|
||||
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const fromFlatServerlessFunctionMaps = getSubFlatEntityMapsOrThrow({
|
||||
flatEntityIds: [existingFlatServerlessFunction.id],
|
||||
flatEntityMaps: existingFlatServerlessFunctionMaps,
|
||||
});
|
||||
const toFlatServerlessFunctionMaps =
|
||||
deleteFlatEntityFromFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: fromFlatServerlessFunctionMaps,
|
||||
entityToDeleteId: existingFlatServerlessFunction.id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatServerlessFunctionMaps: {
|
||||
from: fromFlatServerlessFunctionMaps,
|
||||
to: toFlatServerlessFunctionMaps,
|
||||
},
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingEntities: true,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while destroying serverless function',
|
||||
);
|
||||
}
|
||||
|
||||
this.fileStorageService.delete({
|
||||
folderPath: getServerlessFolder({
|
||||
serverlessFunction: existingFlatServerlessFunction,
|
||||
}),
|
||||
});
|
||||
|
||||
return existingFlatServerlessFunction;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
|
||||
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
|
||||
import {
|
||||
SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES,
|
||||
ServerlessFunctionEntity,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
|
||||
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
|
||||
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceFlatMapCache('flatServerlessFunctionMaps')
|
||||
export class WorkspaceFlatServerlessFunctionMapCacheService extends WorkspaceFlatMapCacheService<
|
||||
FlatEntityMaps<FlatServerlessFunction>
|
||||
> {
|
||||
constructor(
|
||||
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
|
||||
cacheStorageService: CacheStorageService,
|
||||
@InjectRepository(ServerlessFunctionEntity)
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
) {
|
||||
super(cacheStorageService);
|
||||
}
|
||||
|
||||
protected async computeFlatMap({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<FlatEntityMaps<FlatServerlessFunction>> {
|
||||
const serverlessFunctions = await this.serverlessFunctionRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
const flatServerlessFunctionMaps = serverlessFunctions.reduce<
|
||||
FlatEntityMaps<FlatServerlessFunction>
|
||||
>((flatEntityMaps, serverlessFunction) => {
|
||||
const flatServerlessFunction = {
|
||||
...removePropertiesFromRecord(serverlessFunction, [
|
||||
...SERVERLESS_FUNCTION_ENTITY_RELATION_PROPERTIES,
|
||||
]),
|
||||
universalIdentifier: serverlessFunction.universalIdentifier ?? '',
|
||||
} satisfies FlatServerlessFunction;
|
||||
|
||||
return {
|
||||
byId: {
|
||||
...flatEntityMaps.byId,
|
||||
[flatServerlessFunction.id]: flatServerlessFunction,
|
||||
},
|
||||
idByUniversalIdentifier: {
|
||||
...flatEntityMaps.idByUniversalIdentifier,
|
||||
[flatServerlessFunction.universalIdentifier]:
|
||||
flatServerlessFunction.id,
|
||||
},
|
||||
};
|
||||
}, EMPTY_FLAT_ENTITY_MAPS);
|
||||
|
||||
return flatServerlessFunctionMaps;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user