Add more control on http trigger (#21216)

add "new Response" utils to define response code or content type of http
route triggered logic function responses

follow up of https://github.com/twentyhq/twenty/pull/21214

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
martmull
2026-06-04 17:34:10 +02:00
committed by GitHub
parent d3a1781a59
commit e0d42323af
11 changed files with 514 additions and 67 deletions
@@ -2,23 +2,34 @@ import {
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Patch,
Post,
Put,
Req,
Res,
UseFilters,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { Request, Response } from 'express';
import { HTTPMethod } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
import { RouteTriggerRestApiExceptionFilter } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/exceptions/route-trigger-rest-api-exception-filter';
import { RouteTriggerService } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/route-trigger.service';
import {
RouteTriggerResponse,
RouteTriggerService,
} from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/route-trigger.service';
const ALLOWED_RESPONSE_HEADERS = new Set([
'content-type',
'content-language',
'content-disposition',
'cache-control',
'retry-after',
]);
@Controller('s')
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
@@ -27,47 +38,96 @@ export class RouteTriggerController {
constructor(private readonly routeTriggerService: RouteTriggerService) {}
@Get('*path')
@HttpCode(HttpStatus.OK)
async get(@Req() request: Request) {
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.GET,
});
async get(@Req() request: Request, @Res() response: Response) {
this.sendResponse(
response,
await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.GET,
}),
);
}
@Post('*path')
@HttpCode(HttpStatus.OK)
async post(@Req() request: Request) {
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.POST,
});
async post(@Req() request: Request, @Res() response: Response) {
this.sendResponse(
response,
await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.POST,
}),
);
}
@Put('*path')
@HttpCode(HttpStatus.OK)
async put(@Req() request: Request) {
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.PUT,
});
async put(@Req() request: Request, @Res() response: Response) {
this.sendResponse(
response,
await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.PUT,
}),
);
}
@Patch('*path')
@HttpCode(HttpStatus.OK)
async patch(@Req() request: Request) {
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.PATCH,
});
async patch(@Req() request: Request, @Res() response: Response) {
this.sendResponse(
response,
await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.PATCH,
}),
);
}
@Delete('*path')
@HttpCode(HttpStatus.OK)
async delete(@Req() request: Request) {
return await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.DELETE,
});
async delete(@Req() request: Request, @Res() response: Response) {
this.sendResponse(
response,
await this.routeTriggerService.handle({
request,
httpMethod: HTTPMethod.DELETE,
}),
);
}
private sendResponse(
response: Response,
{ statusCode, headers, body }: RouteTriggerResponse,
) {
response.status(statusCode);
for (const [key, value] of Object.entries(headers)) {
if (ALLOWED_RESPONSE_HEADERS.has(key.toLowerCase())) {
response.setHeader(key, value);
}
}
if (!isDefined(body)) {
response.send();
return;
}
const hasContentType = isDefined(response.getHeader('content-type'));
if (typeof body === 'string') {
if (!hasContentType) {
response.setHeader('content-type', 'text/plain');
}
response.send(body);
return;
}
if (hasContentType) {
response.send(JSON.stringify(body));
return;
}
response.json(body);
}
}