diff --git a/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts b/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts index a564460fc..beb53c4d0 100644 --- a/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts @@ -32,7 +32,9 @@ export class FileController { const workspaceId = (req as any)?.workspaceId; if (!workspaceId) { - return res.status(401).send({ error: 'Unauthorized' }); + return res + .status(401) + .send({ error: 'Unauthorized, missing workspaceId' }); } try { diff --git a/packages/twenty-server/src/engine/integrations/serverless/drivers/base-serverless.driver.ts b/packages/twenty-server/src/engine/integrations/serverless/drivers/base-serverless.driver.ts index cfae10d6b..e26666dc5 100644 --- a/packages/twenty-server/src/engine/integrations/serverless/drivers/base-serverless.driver.ts +++ b/packages/twenty-server/src/engine/integrations/serverless/drivers/base-serverless.driver.ts @@ -2,17 +2,17 @@ import { join } from 'path'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; -import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name'; -import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content'; -import { compileTypescript } from 'src/engine/integrations/serverless/drivers/utils/compile-typescript'; import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service'; +import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content'; +import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name'; +import { compileTypescript } from 'src/engine/integrations/serverless/drivers/utils/compile-typescript'; +import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; export class BaseServerlessDriver { getFolderPath(serverlessFunction: ServerlessFunctionEntity) { return join( + 'workspace-' + serverlessFunction.workspaceId, FileFolder.ServerlessFunction, - serverlessFunction.workspaceId, serverlessFunction.id, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.interceptor.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.interceptor.ts new file mode 100644 index 000000000..7120a5c5f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.interceptor.ts @@ -0,0 +1,57 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; + +import { Observable, map } from 'rxjs'; + +import { FileService } from 'src/engine/core-modules/file/services/file.service'; + +@Injectable() +export class ServerlessFunctionInterceptor implements NestInterceptor { + constructor(private readonly fileService: FileService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + map(async (data) => { + if (data.edges && Array.isArray(data.edges)) { + return { + ...data, + edges: Promise.all( + data.edges.map((item) => ({ + ...item, + node: this.processItem(item.node), + })), + ), + }; + } else { + return this.processItem(data); + } + }), + ); + } + + private async processItem(item: any): Promise { + if (item && item.sourceCodeFullPath) { + const workspaceId = item.workspace?.id || item.workspaceId; + + if (!workspaceId) { + return item; + } + + const signedPayload = await this.fileService.encodeFileToken({ + serverlessFunctionId: item.id, + workspace_id: workspaceId, + }); + + return { + ...item, + sourceCodeFullPath: `${item.sourceCodeFullPath}?token=${signedPayload}`, + }; + } + + return item; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts index 5f2d3ac85..1fdbdfe5a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts @@ -1,21 +1,23 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { SortDirection } from '@ptc-org/nestjs-query-core'; import { NestjsQueryGraphQLModule, PagingStrategies, } from '@ptc-org/nestjs-query-graphql'; -import { SortDirection } from '@ptc-org/nestjs-query-core'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; -import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { ServerlessModule } from 'src/engine/integrations/serverless/serverless.module'; -import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; -import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver'; -import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; -import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto'; -import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; +import { FileModule } from 'src/engine/core-modules/file/file.module'; +import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; +import { ServerlessModule } from 'src/engine/integrations/serverless/serverless.module'; +import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto'; +import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; +import { ServerlessFunctionInterceptor } from 'src/engine/metadata-modules/serverless-function/serverless-function.interceptor'; +import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver'; +import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; @Module({ imports: [ @@ -27,6 +29,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature- 'metadata', ), TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), + FileModule, ], services: [ServerlessFunctionService], resolvers: [ @@ -42,6 +45,7 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature- update: { disabled: true }, delete: { disabled: true }, guards: [JwtAuthGuard], + interceptors: [ServerlessFunctionInterceptor], }, ], }), diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts index 9e59b0bf5..b49956344 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts @@ -1,4 +1,4 @@ -import { UseGuards } from '@nestjs/common'; +import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; @@ -21,6 +21,7 @@ import { ServerlessFunctionException, ServerlessFunctionExceptionCode, } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception'; +import { ServerlessFunctionInterceptor } from 'src/engine/metadata-modules/serverless-function/serverless-function.interceptor'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils'; @@ -66,6 +67,7 @@ export class ServerlessFunctionResolver { } } + @UseInterceptors(ServerlessFunctionInterceptor) @Mutation(() => ServerlessFunctionDTO) async updateOneServerlessFunction( @Args('input') @@ -84,6 +86,7 @@ export class ServerlessFunctionResolver { } } + @UseInterceptors(ServerlessFunctionInterceptor) @Mutation(() => ServerlessFunctionDTO) async createOneServerlessFunction( @Args('input') @@ -106,6 +109,7 @@ export class ServerlessFunctionResolver { } } + @UseInterceptors(ServerlessFunctionInterceptor) @Mutation(() => ServerlessFunctionDTO) async createOneServerlessFunctionFromFile( @Args({ name: 'file', type: () => GraphQLUpload }) diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts index 2fa2e0b8e..38768d44a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts @@ -3,15 +3,20 @@ import { InjectRepository } from '@nestjs/typeorm'; import { join } from 'path'; +import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; 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 { ServerlessExecuteResult } from 'src/engine/integrations/serverless/drivers/interfaces/serverless-driver.interface'; +import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service'; +import { readFileContent } from 'src/engine/integrations/file-storage/utils/read-file-content'; +import { SOURCE_FILE_NAME } from 'src/engine/integrations/serverless/drivers/constants/source-file-name'; import { ServerlessService } from 'src/engine/integrations/serverless/serverless.service'; +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'; import { ServerlessFunctionEntity, ServerlessFunctionSyncStatus, @@ -20,12 +25,7 @@ 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 { @@ -119,8 +119,8 @@ export class ServerlessFunctionService extends TypeOrmQueryService +### Serverless functions +This feature is WIP and is not yet useful for most users. + + + + ### Support Chat