Fix env not optional + serverless logging (#15186)

Several fixes after discussing with @BOHEUS 
- set applicationManifest env key optional
- fix server local serverless function logging (introduces a new env
variable `SERVERLESS_LOGS_ENABLED` defaulting to false)
This commit is contained in:
martmull
2025-10-20 15:06:26 +02:00
committed by GitHub
parent 3d7f332f80
commit 11564f135e
11 changed files with 145 additions and 91 deletions
@@ -0,0 +1,59 @@
import {
type ArgumentsHost,
Catch,
type ExceptionFilter,
} from '@nestjs/common';
import type { Response } from 'express';
import {
RouteTriggerException,
RouteTriggerExceptionCode,
} from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception';
import type { CustomException } from 'src/utils/custom-exception';
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
@Catch(RouteTriggerException)
export class RouteTriggerRestApiExceptionFilter implements ExceptionFilter {
constructor(
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
) {}
catch(exception: RouteTriggerException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
switch (exception.code) {
case RouteTriggerExceptionCode.WORKSPACE_NOT_FOUND:
case RouteTriggerExceptionCode.ROUTE_NOT_FOUND:
case RouteTriggerExceptionCode.TRIGGER_NOT_FOUND:
case RouteTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
return this.httpExceptionHandlerService.handleError(
exception as CustomException,
response,
404,
);
case RouteTriggerExceptionCode.FORBIDDEN_EXCEPTION:
return this.httpExceptionHandlerService.handleError(
exception as CustomException,
response,
403,
);
case RouteTriggerExceptionCode.SERVERLESS_FUNCTION_EXECUTION_ERROR:
return this.httpExceptionHandlerService.handleError(
exception as CustomException,
response,
500,
);
case RouteTriggerExceptionCode.ROUTE_ALREADY_EXIST:
case RouteTriggerExceptionCode.ROUTE_PATH_ALREADY_EXIST:
default: {
return this.httpExceptionHandlerService.handleError(
exception as CustomException,
response,
400,
);
}
}
}
}
@@ -8,9 +8,12 @@ export class RouteTriggerException extends CustomException {
}
export enum RouteTriggerExceptionCode {
WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND',
ROUTE_NOT_FOUND = 'ROUTE_NOT_FOUND',
ROUTE_ALREADY_EXIST = 'ROUTE_ALREADY_EXIST',
ROUTE_INVALID = 'ROUTE_INVALID',
ROUTE_PATH_ALREADY_EXIST = 'ROUTE_PATH_ALREADY_EXIST',
TRIGGER_NOT_FOUND = 'TRIGGER_NOT_FOUND',
SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND',
ROUTE_ALREADY_EXIST = 'ROUTE_ALREADY_EXIST',
ROUTE_PATH_ALREADY_EXIST = 'ROUTE_PATH_ALREADY_EXIST',
FORBIDDEN_EXCEPTION = 'FORBIDDEN_EXCEPTION',
SERVERLESS_FUNCTION_EXECUTION_ERROR = 'SERVERLESS_FUNCTION_EXECUTION_ERROR',
}
@@ -6,6 +6,7 @@ import {
Post,
Put,
Req,
UseFilters,
UseGuards,
} from '@nestjs/common';
@@ -14,60 +15,51 @@ import { Request } from 'express';
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
import { HTTPMethod } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { RouteTriggerService } from 'src/engine/metadata-modules/route-trigger/route-trigger.service';
import { formatServerlessControllerResponse } from 'src/engine/metadata-modules/route-trigger/utils/format-serverless-controller-response';
import { RouteTriggerRestApiExceptionFilter } from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger-rest-api-exception-filter';
@Controller('s')
@UseGuards(PublicEndpointGuard)
@UseFilters(RouteTriggerRestApiExceptionFilter)
export class RouteTriggerController {
constructor(private readonly routeTriggerService: RouteTriggerService) {}
@Get('*')
async get(@Req() request: Request) {
const result = await this.routeTriggerService.handle({
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.GET,
});
return formatServerlessControllerResponse(result);
}
@Post('*')
async post(@Req() request: Request) {
const result = await this.routeTriggerService.handle({
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.POST,
});
return formatServerlessControllerResponse(result);
}
@Put('*')
async put(@Req() request: Request) {
const result = await this.routeTriggerService.handle({
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.PUT,
});
return formatServerlessControllerResponse(result);
}
@Patch('*')
async patch(@Req() request: Request) {
const result = await this.routeTriggerService.handle({
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.PATCH,
});
return formatServerlessControllerResponse(result);
}
@Delete('*')
async delete(@Req() request: Request) {
const result = await this.routeTriggerService.handle({
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.DELETE,
});
return formatServerlessControllerResponse(result);
}
}
@@ -1,8 +1,4 @@
import {
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@@ -16,11 +12,11 @@ import {
} from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import {
RouteTriggerException,
RouteTriggerExceptionCode,
} from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception';
@Injectable()
export class RouteTriggerService {
@@ -51,9 +47,9 @@ export class RouteTriggerService {
assertIsDefinedOrThrow(
workspace,
new AuthException(
new RouteTriggerException(
'Workspace not found',
AuthExceptionCode.WORKSPACE_NOT_FOUND,
RouteTriggerExceptionCode.WORKSPACE_NOT_FOUND,
),
);
@@ -81,7 +77,10 @@ export class RouteTriggerService {
}
}
throw new NotFoundException('No Route trigger found');
throw new RouteTriggerException(
'No Route trigger found',
RouteTriggerExceptionCode.TRIGGER_NOT_FOUND,
);
}
private async validateWorkspaceFromRequest({
@@ -95,11 +94,17 @@ export class RouteTriggerService {
await this.accessTokenService.validateTokenByRequest(request);
if (!isDefined(workspace)) {
throw new NotFoundException('Workspace not found');
throw new RouteTriggerException(
'Workspace not found',
RouteTriggerExceptionCode.WORKSPACE_NOT_FOUND,
);
}
if (workspace.id !== workspaceId) {
throw new ForbiddenException('Invalid Workspace');
throw new RouteTriggerException(
'You are not authorized',
RouteTriggerExceptionCode.FORBIDDEN_EXCEPTION,
);
}
}
@@ -133,11 +138,25 @@ export class RouteTriggerService {
...routeTriggerWithPathParams.pathParams,
};
return await this.serverlessFunctionService.executeOneServerlessFunction(
routeTriggerWithPathParams.routeTrigger.serverlessFunction.id,
routeTriggerWithPathParams.routeTrigger.workspaceId,
executionParams,
'draft',
);
const result =
await this.serverlessFunctionService.executeOneServerlessFunction(
routeTriggerWithPathParams.routeTrigger.serverlessFunction.id,
routeTriggerWithPathParams.routeTrigger.workspaceId,
executionParams,
'draft',
);
if (!isDefined(result)) {
return result;
}
if (result.error) {
throw new RouteTriggerException(
result.error.errorMessage,
RouteTriggerExceptionCode.SERVERLESS_FUNCTION_EXECUTION_ERROR,
);
}
return result.data;
}
}
@@ -1,22 +0,0 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { type ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
export const formatServerlessControllerResponse = (
result?: ServerlessExecuteResult | null,
) => {
if (!isDefined(result)) {
return result;
}
if (result.error) {
throw new HttpException(
{ errorType: result.error.errorType, message: result.error.errorMessage },
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
return result.data;
};