Files
twenty/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts
martmull 00fea17920 Serverless function UI (#6388)
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
2024-07-29 13:03:09 +02:00

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);
}
}