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:
nitin
2025-07-23 18:41:53 +05:30
committed by GitHub
parent 05a09d7a73
commit 0e561e4ef4
17 changed files with 302 additions and 68 deletions

View File

@ -17,10 +17,9 @@ import {
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
import { CreateManyResolverFactory } from './factories/create-many-resolver.factory';
import { CreateOneResolverFactory } from './factories/create-one-resolver.factory';
@ -100,27 +99,14 @@ export class WorkspaceResolverFactory {
for (const objectMetadata of Object.values(objectMetadataMaps.byId).filter(
isDefined,
)) {
const workspaceEntity = standardObjectMetadataDefinitions.find(
(entity) => {
const entityMetadata = metadataArgsStorage.filterEntities(entity);
return entityMetadata?.standardId === objectMetadata.standardId;
},
);
if (workspaceEntity) {
const entityMetadata =
metadataArgsStorage.filterEntities(workspaceEntity);
if (
isGatedAndNotEnabled(
entityMetadata?.gate,
workspaceFeatureFlagsMap,
'graphql',
)
) {
continue;
}
if (
shouldExcludeFromWorkspaceApi(
objectMetadata,
standardObjectMetadataDefinitions,
workspaceFeatureFlagsMap,
)
) {
continue;
}
// Generate query resolvers

View File

@ -21,10 +21,9 @@ import {
WorkspaceMetadataCacheExceptionCode,
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
@Injectable()
export class WorkspaceSchemaFactory {
@ -86,27 +85,10 @@ export class WorkspaceSchemaFactory {
indexes: objectMetadataItem.indexMetadatas,
}))
.filter((objectMetadata) => {
// Find the corresponding workspace entity for this object metadata
const workspaceEntity = standardObjectMetadataDefinitions.find(
(entity) => {
const entityMetadata = metadataArgsStorage.filterEntities(entity);
return entityMetadata?.standardId === objectMetadata.standardId;
},
);
if (!workspaceEntity) {
return true; // Include non-workspace entities (custom objects, etc.)
}
const entityMetadata =
metadataArgsStorage.filterEntities(workspaceEntity);
// Filter out entities that are GraphQL-gated and not enabled
return !isGatedAndNotEnabled(
entityMetadata?.gate,
return !shouldExcludeFromWorkspaceApi(
objectMetadata,
standardObjectMetadataDefinitions,
workspaceFeatureFlagsMap,
'graphql',
);
});

View File

@ -12,25 +12,29 @@ import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path
import { Query } from 'src/engine/api/rest/core/types/query.type';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNamePlural } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-plural.util';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
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';
@Injectable()
export class CoreQueryBuilderFactory {
constructor(
private readonly createManyQueryFactory: CreateManyQueryFactory,
private readonly findDuplicatesQueryFactory: FindDuplicatesQueryFactory,
private readonly createVariablesFactory: CreateVariablesFactory,
private readonly findDuplicatesQueryFactory: FindDuplicatesQueryFactory,
private readonly findDuplicatesVariablesFactory: FindDuplicatesVariablesFactory,
private readonly accessTokenService: AccessTokenService,
private readonly domainManagerService: DomainManagerService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
private readonly featureFlagService: FeatureFlagService,
) {}
async getObjectMetadata(
@ -94,6 +98,22 @@ export class CoreQueryBuilderFactory {
);
}
const workspaceFeatureFlagsMap =
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspace.id);
// Check if this entity is workspace-gated and should be blocked from workspace API
if (
shouldExcludeFromWorkspaceApi(
objectMetadataItem,
standardObjectMetadataDefinitions,
workspaceFeatureFlagsMap,
)
) {
throw new BadRequestException(
`object '${parsedObject}' not found. ${parsedObject} is not available via REST API.`,
);
}
return {
objectMetadataMaps,
objectMetadataMapItem: objectMetadataItem,

View File

@ -4,6 +4,7 @@ import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@ -11,6 +12,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
imports: [
AuthModule,
DomainManagerModule,
FeatureFlagModule,
WorkspaceCacheStorageModule,
WorkspaceMetadataCacheModule,
],