https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=36235-120877 Did not do the file manager part. A Function is defined using one unique file at the moment Feature protected by featureFlag `IS_FUNCTION_SETTINGS_ENABLED` ## Demo https://github.com/user-attachments/assets/0acb8291-47b4-4521-a6fa-a88b9198609b
197 lines
6.2 KiB
TypeScript
197 lines
6.2 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
|
|
import { join } from 'path';
|
|
|
|
import { FileUpload } from 'graphql-upload';
|
|
import { Repository } from 'typeorm';
|
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
|
import { v4 } from 'uuid';
|
|
|
|
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
|
|
|
import { ServerlessService } from 'src/engine/integrations/serverless/serverless.service';
|
|
import {
|
|
ServerlessFunctionEntity,
|
|
ServerlessFunctionSyncStatus,
|
|
} from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
|
import {
|
|
ServerlessFunctionException,
|
|
ServerlessFunctionExceptionCode,
|
|
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
|
import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content';
|
|
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
|
import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name';
|
|
import { serverlessFunctionCreateHash } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-create-hash.utils';
|
|
import { CreateServerlessFunctionFromFileInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function-from-file.input';
|
|
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
|
|
|
@Injectable()
|
|
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
|
|
constructor(
|
|
private readonly fileStorageService: FileStorageService,
|
|
private readonly serverlessService: ServerlessService,
|
|
@InjectRepository(ServerlessFunctionEntity, 'metadata')
|
|
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
|
) {
|
|
super(serverlessFunctionRepository);
|
|
}
|
|
|
|
async executeOne(
|
|
id: string,
|
|
workspaceId: string,
|
|
payload: object | undefined = undefined,
|
|
) {
|
|
const functionToExecute = await this.serverlessFunctionRepository.findOne({
|
|
where: {
|
|
id,
|
|
workspaceId,
|
|
},
|
|
});
|
|
|
|
if (!functionToExecute) {
|
|
throw new ServerlessFunctionException(
|
|
`Function does not exist`,
|
|
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
if (
|
|
functionToExecute.syncStatus === ServerlessFunctionSyncStatus.NOT_READY
|
|
) {
|
|
throw new ServerlessFunctionException(
|
|
`Function is not ready to be executed`,
|
|
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
return this.serverlessService.execute(functionToExecute, payload);
|
|
}
|
|
|
|
async deleteOneServerlessFunction(id: string, workspaceId: string) {
|
|
const existingServerlessFunction =
|
|
await this.serverlessFunctionRepository.findOne({
|
|
where: { id, workspaceId },
|
|
});
|
|
|
|
if (!existingServerlessFunction) {
|
|
throw new ServerlessFunctionException(
|
|
`Function does not exist`,
|
|
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
await super.deleteOne(id);
|
|
|
|
await this.serverlessService.delete(existingServerlessFunction);
|
|
|
|
return existingServerlessFunction;
|
|
}
|
|
|
|
async updateOneServerlessFunction(
|
|
serverlessFunctionInput: UpdateServerlessFunctionInput,
|
|
workspaceId: string,
|
|
) {
|
|
const existingServerlessFunction =
|
|
await this.serverlessFunctionRepository.findOne({
|
|
where: { id: serverlessFunctionInput.id, workspaceId },
|
|
});
|
|
|
|
if (!existingServerlessFunction) {
|
|
throw new ServerlessFunctionException(
|
|
`Function does not exist`,
|
|
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
const codeHasChanged =
|
|
serverlessFunctionCreateHash(serverlessFunctionInput.code) !==
|
|
existingServerlessFunction.sourceCodeHash;
|
|
|
|
await super.updateOne(existingServerlessFunction.id, {
|
|
name: serverlessFunctionInput.name,
|
|
description: serverlessFunctionInput.description,
|
|
sourceCodeHash: serverlessFunctionCreateHash(
|
|
serverlessFunctionInput.code,
|
|
),
|
|
});
|
|
|
|
if (codeHasChanged) {
|
|
const fileFolder = join(
|
|
FileFolder.ServerlessFunction,
|
|
workspaceId,
|
|
existingServerlessFunction.id,
|
|
);
|
|
|
|
await this.fileStorageService.write({
|
|
file: serverlessFunctionInput.code,
|
|
name: SOURCE_FILE_NAME,
|
|
mimeType: undefined,
|
|
folder: fileFolder,
|
|
});
|
|
|
|
await this.serverlessService.build(existingServerlessFunction);
|
|
}
|
|
|
|
return await this.findById(existingServerlessFunction.id);
|
|
}
|
|
|
|
async createOneServerlessFunction(
|
|
serverlessFunctionInput: CreateServerlessFunctionFromFileInput,
|
|
code: FileUpload | string,
|
|
workspaceId: string,
|
|
) {
|
|
const existingServerlessFunction =
|
|
await this.serverlessFunctionRepository.findOne({
|
|
where: { name: serverlessFunctionInput.name, workspaceId },
|
|
});
|
|
|
|
if (existingServerlessFunction) {
|
|
throw new ServerlessFunctionException(
|
|
`Function already exists`,
|
|
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST,
|
|
);
|
|
}
|
|
|
|
let typescriptCode: string;
|
|
|
|
if (typeof code === 'string') {
|
|
typescriptCode = code;
|
|
} else {
|
|
typescriptCode = await readFileContent(code.createReadStream());
|
|
}
|
|
|
|
const serverlessFunctionId = v4();
|
|
|
|
const fileFolder = join(
|
|
FileFolder.ServerlessFunction,
|
|
workspaceId,
|
|
serverlessFunctionId,
|
|
);
|
|
|
|
const sourceCodeFullPath = fileFolder + '/' + SOURCE_FILE_NAME;
|
|
|
|
const serverlessFunction = await super.createOne({
|
|
...serverlessFunctionInput,
|
|
id: serverlessFunctionId,
|
|
workspaceId,
|
|
sourceCodeHash: serverlessFunctionCreateHash(typescriptCode),
|
|
sourceCodeFullPath,
|
|
});
|
|
|
|
await this.fileStorageService.write({
|
|
file: typescriptCode,
|
|
name: SOURCE_FILE_NAME,
|
|
mimeType: undefined,
|
|
folder: fileFolder,
|
|
});
|
|
|
|
await this.serverlessService.build(serverlessFunction);
|
|
await super.updateOne(serverlessFunctionId, {
|
|
syncStatus: ServerlessFunctionSyncStatus.READY,
|
|
});
|
|
|
|
return await this.findById(serverlessFunctionId);
|
|
}
|
|
}
|