Fix missing components.schema for webhooks (#13433)

Webhooks are still documented in core playground to detail Webhooks of
core objects.
We moved webhooks to metadata playground and forgot to keep computation
of WebhookForResponse

This PR adds it back
This commit is contained in:
martmull
2025-07-25 16:36:11 +02:00
committed by GitHub
parent d1b11bafe6
commit 9380a1386a
3 changed files with 53 additions and 25 deletions

View File

@ -81,6 +81,7 @@ export const RestPlayground = ({ onError, schema }: RestPlaygroundProps) => {
forceDarkModeState: theme.name === 'dark' ? 'dark' : 'light', forceDarkModeState: theme.name === 'dark' ? 'dark' : 'light',
hideClientButton: true, hideClientButton: true,
hideDarkModeToggle: true, hideDarkModeToggle: true,
hideModels: schema === 'metadata',
pathRouting: { pathRouting: {
basePath: getSettingsPath(SettingsPath.RestPlayground, { basePath: getSettingsPath(SettingsPath.RestPlayground, {
schema, schema,

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { Request } from 'express'; import { Request } from 'express';
import { OpenAPIV3_1 } from 'openapi-types'; import { OpenAPIV3_1 } from 'openapi-types';
import { capitalize } from 'twenty-shared/utils'; import { capitalize, isDefined } from 'twenty-shared/utils';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
@ -42,6 +42,7 @@ import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metada
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects'; import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util'; import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
import { getServerUrl } from 'src/utils/get-server-url'; import { getServerUrl } from 'src/utils/get-server-url';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Injectable() @Injectable()
export class OpenApiService { export class OpenApiService {
@ -52,6 +53,30 @@ export class OpenApiService {
private readonly featureFlagService: FeatureFlagService, private readonly featureFlagService: FeatureFlagService,
) {} ) {}
private async getWorkspaceFromRequest(request: Request) {
try {
const { workspace } =
await this.accessTokenService.validateTokenByRequest(request);
workspaceValidator.assertIsDefinedOrThrow(workspace);
return workspace;
} catch (e) {
return null;
}
}
private async getObjectMetadataItems(workspace: Workspace) {
return await this.objectMetadataService.findManyWithinWorkspace(
workspace.id,
{
order: {
namePlural: 'ASC',
},
},
);
}
async generateCoreSchema(request: Request): Promise<OpenAPIV3_1.Document> { async generateCoreSchema(request: Request): Promise<OpenAPIV3_1.Document> {
const baseUrl = getServerUrl( const baseUrl = getServerUrl(
this.twentyConfigService.get('SERVER_URL'), this.twentyConfigService.get('SERVER_URL'),
@ -60,26 +85,14 @@ export class OpenApiService {
const schema = baseSchema('core', baseUrl); const schema = baseSchema('core', baseUrl);
let objectMetadataItems; const workspace = await this.getWorkspaceFromRequest(request);
let workspace;
try { if (!isDefined(workspace)) {
const authResult =
await this.accessTokenService.validateTokenByRequest(request);
workspace = authResult.workspace;
workspaceValidator.assertIsDefinedOrThrow(workspace);
objectMetadataItems =
await this.objectMetadataService.findManyWithinWorkspace(workspace.id, {
order: {
namePlural: 'ASC',
},
});
} catch (err) {
return schema; return schema;
} }
const objectMetadataItems = await this.getObjectMetadataItems(workspace);
if (!objectMetadataItems.length) { if (!objectMetadataItems.length) {
return schema; return schema;
} }
@ -105,7 +118,7 @@ export class OpenApiService {
return paths; return paths;
}, schema.paths as OpenAPIV3_1.PathsObject); }, schema.paths as OpenAPIV3_1.PathsObject);
schema.webhooks = objectMetadataItems.reduce( schema.webhooks = filteredObjectMetadataItems.reduce(
(paths, item) => { (paths, item) => {
paths[ paths[
this.createWebhookEventName( this.createWebhookEventName(
@ -134,8 +147,6 @@ export class OpenApiService {
>, >,
); );
schema.tags = computeSchemaTags(objectMetadataItems);
schema.components = { schema.components = {
...schema.components, // components.securitySchemes is defined in base Schema ...schema.components, // components.securitySchemes is defined in base Schema
schemas: computeSchemaComponents(filteredObjectMetadataItems), schemas: computeSchemaComponents(filteredObjectMetadataItems),
@ -161,7 +172,11 @@ export class OpenApiService {
const schema = baseSchema('metadata', baseUrl); const schema = baseSchema('metadata', baseUrl);
schema.tags = [{ name: 'placeholder' }]; const workspace = await this.getWorkspaceFromRequest(request);
if (!isDefined(workspace)) {
return schema;
}
const metadata = [ const metadata = [
{ {
@ -177,7 +192,7 @@ export class OpenApiService {
namePlural: 'webhooks', namePlural: 'webhooks',
}, },
{ {
nameSingular: 'apikey', nameSingular: 'apiKey',
namePlural: 'apiKeys', namePlural: 'apiKeys',
}, },
]; ];
@ -249,9 +264,18 @@ export class OpenApiService {
return path; return path;
}, schema.paths as OpenAPIV3_1.PathsObject); }, schema.paths as OpenAPIV3_1.PathsObject);
const objectMetadataItems = await this.getObjectMetadataItems(workspace);
const webhookAndApiKeyObjectMetadataItems = objectMetadataItems.filter(
({ nameSingular }) => ['webhook', 'apiKey'].includes(nameSingular),
);
schema.components = { schema.components = {
...schema.components, // components.securitySchemes is defined in base Schema ...schema.components, // components.securitySchemes is defined in base Schema
schemas: computeMetadataSchemaComponents(metadata), schemas: {
...computeMetadataSchemaComponents(metadata),
...computeSchemaComponents(webhookAndApiKeyObjectMetadataItems),
},
parameters: computeParameterComponents(true), parameters: computeParameterComponents(true),
responses: { responses: {
'400': get400ErrorResponses(), '400': get400ErrorResponses(),

View File

@ -239,10 +239,13 @@ export const getJsonResponse = () => {
servers: { servers: {
type: 'array', type: 'array',
items: { items: {
type: 'object',
properties: {
url: { type: 'string' }, url: { type: 'string' },
description: { type: 'string' }, description: { type: 'string' },
}, },
}, },
},
components: { components: {
type: 'object', type: 'object',
properties: { properties: {