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
@@ -0,0 +1,49 @@
import { LOGIC_FUNCTION_HTTP_RESPONSE_MARKER } from 'twenty-shared/types';
import { buildRouteTriggerResponse } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/route-trigger.service';
describe('buildRouteTriggerResponse', () => {
it('wraps a plain body with status 200 and no headers', () => {
expect(buildRouteTriggerResponse({ message: 'hi' })).toEqual({
statusCode: 200,
headers: {},
body: { message: 'hi' },
});
});
it('passes through null/undefined as a 200 with that body', () => {
expect(buildRouteTriggerResponse(null)).toEqual({
statusCode: 200,
headers: {},
body: null,
});
});
it('reads status, headers and body from a wrapped response', () => {
const data = {
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
body: '<h1>Hi</h1>',
status: 201,
headers: { 'Content-Type': 'text/html' },
};
expect(buildRouteTriggerResponse(data)).toEqual({
statusCode: 201,
headers: { 'Content-Type': 'text/html' },
body: '<h1>Hi</h1>',
});
});
it('defaults a wrapped response without status/headers to 200 and {}', () => {
const data = {
[LOGIC_FUNCTION_HTTP_RESPONSE_MARKER]: true,
body: { ok: true },
};
expect(buildRouteTriggerResponse(data)).toEqual({
statusCode: 200,
headers: {},
body: { ok: true },
});
});
});
@@ -5,7 +5,7 @@ import { Request } from 'express';
import { match } from 'path-to-regexp';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { IsNull, Not, Repository } from 'typeorm';
import { HTTPMethod } from 'twenty-shared/types';
import { HTTPMethod, isLogicFunctionHttpResponse } from 'twenty-shared/types';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
@@ -26,6 +26,26 @@ import {
} from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service';
import { CustomException } from 'src/utils/custom-exception';
export type RouteTriggerResponse = {
statusCode: number;
headers: Record<string, string>;
body: unknown;
};
export const buildRouteTriggerResponse = (
data: unknown,
): RouteTriggerResponse => {
if (isLogicFunctionHttpResponse(data)) {
return {
statusCode: data.status ?? 200,
headers: data.headers ?? {},
body: data.body,
};
}
return { statusCode: 200, headers: {}, body: data };
};
@Injectable()
export class RouteTriggerService {
private readonly logger = new Logger(RouteTriggerService.name);
@@ -223,7 +243,7 @@ export class RouteTriggerService {
}
if (!isDefined(result)) {
return result;
return buildRouteTriggerResponse(result);
}
if (result.error) {
@@ -233,6 +253,6 @@ export class RouteTriggerService {
);
}
return result.data;
return buildRouteTriggerResponse(result.data);
}
}