fix: migrate webhook and API key REST endpoints to core schema (#13318)
## Problem
After migrating webhooks and API keys from workspace to core level, REST
API endpoints were still creating entities in workspace schema
(`workspace_*`) instead of core schema, causing webhooks to not fire.
## Solution
- Added dedicated REST controllers for webhooks (`/rest/webhooks`) and
API keys (`/rest/apiKeys`)
- Updated dynamic controller to block workspace-gated entities from
being processed
- Fixed OpenAPI documentation to exclude these endpoints from playground
- Ensured return formats match GraphQL resolvers exactly
## Testing
✅ All endpoints tested with provided auth token - webhooks and API keys
now correctly stored in `core` schema
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { OpenApiController } from 'src/engine/core-modules/open-api/open-api.controller';
|
||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
|
||||
@Module({
|
||||
imports: [ObjectMetadataModule, AuthModule],
|
||||
imports: [ObjectMetadataModule, AuthModule, FeatureFlagModule],
|
||||
controllers: [OpenApiController],
|
||||
providers: [OpenApiService],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { OpenApiService } from 'src/engine/core-modules/open-api/open-api.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
@@ -24,11 +25,16 @@ describe('OpenApiService', () => {
|
||||
provide: TwentyConfigService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: FeatureFlagService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<OpenApiService>(OpenApiService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
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 { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { baseSchema } from 'src/engine/core-modules/open-api/utils/base-schema.utils';
|
||||
import {
|
||||
computeMetadataSchemaComponents,
|
||||
@@ -36,9 +37,11 @@ import {
|
||||
getUpdateOneResponse200,
|
||||
} from 'src/engine/core-modules/open-api/utils/responses.utils';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { getServerUrl } from 'src/utils/get-server-url';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
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 { getServerUrl } from 'src/utils/get-server-url';
|
||||
|
||||
@Injectable()
|
||||
export class OpenApiService {
|
||||
@@ -46,6 +49,7 @@ export class OpenApiService {
|
||||
private readonly accessTokenService: AccessTokenService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async generateCoreSchema(request: Request): Promise<OpenAPIV3_1.Document> {
|
||||
@@ -57,11 +61,13 @@ export class OpenApiService {
|
||||
const schema = baseSchema('core', baseUrl);
|
||||
|
||||
let objectMetadataItems;
|
||||
let workspace;
|
||||
|
||||
try {
|
||||
const { workspace } =
|
||||
const authResult =
|
||||
await this.accessTokenService.validateTokenByRequest(request);
|
||||
|
||||
workspace = authResult.workspace;
|
||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
||||
|
||||
objectMetadataItems =
|
||||
@@ -77,7 +83,19 @@ export class OpenApiService {
|
||||
if (!objectMetadataItems.length) {
|
||||
return schema;
|
||||
}
|
||||
schema.paths = objectMetadataItems.reduce((paths, item) => {
|
||||
|
||||
const workspaceFeatureFlagsMap =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspace.id);
|
||||
|
||||
const filteredObjectMetadataItems = objectMetadataItems.filter((item) => {
|
||||
return !shouldExcludeFromWorkspaceApi(
|
||||
item,
|
||||
standardObjectMetadataDefinitions,
|
||||
workspaceFeatureFlagsMap,
|
||||
);
|
||||
});
|
||||
|
||||
schema.paths = filteredObjectMetadataItems.reduce((paths, item) => {
|
||||
paths[`/${item.namePlural}`] = computeManyResultPath(item);
|
||||
paths[`/batch/${item.namePlural}`] = computeBatchPath(item);
|
||||
paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item);
|
||||
@@ -120,7 +138,7 @@ export class OpenApiService {
|
||||
|
||||
schema.components = {
|
||||
...schema.components, // components.securitySchemes is defined in base Schema
|
||||
schemas: computeSchemaComponents(objectMetadataItems),
|
||||
schemas: computeSchemaComponents(filteredObjectMetadataItems),
|
||||
parameters: computeParameterComponents(),
|
||||
responses: {
|
||||
'400': get400ErrorResponses(),
|
||||
@@ -128,6 +146,8 @@ export class OpenApiService {
|
||||
},
|
||||
};
|
||||
|
||||
schema.tags = computeSchemaTags(filteredObjectMetadataItems);
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
@@ -152,6 +172,14 @@ export class OpenApiService {
|
||||
nameSingular: 'field',
|
||||
namePlural: 'fields',
|
||||
},
|
||||
{
|
||||
nameSingular: 'webhook',
|
||||
namePlural: 'webhooks',
|
||||
},
|
||||
{
|
||||
nameSingular: 'apikey',
|
||||
namePlural: 'apiKeys',
|
||||
},
|
||||
];
|
||||
|
||||
schema.paths = metadata.reduce((path, item) => {
|
||||
|
||||
Reference in New Issue
Block a user