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,
],

View File

@ -4,11 +4,21 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApiKeyResolver } from 'src/engine/core-modules/api-key/api-key.resolver';
import { ApiKeyService } from 'src/engine/core-modules/api-key/api-key.service';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { ApiKeyController } from './controllers/api-key.controller';
@Module({
imports: [TypeOrmModule.forFeature([ApiKey], 'core'), JwtModule],
imports: [
TypeOrmModule.forFeature([ApiKey], 'core'),
JwtModule,
AuthModule,
WorkspaceCacheStorageModule,
],
providers: [ApiKeyService, ApiKeyResolver],
controllers: [ApiKeyController],
exports: [ApiKeyService, TypeOrmModule],
})
export class ApiKeyModule {}

View File

@ -0,0 +1,89 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
UseFilters,
UseGuards,
} from '@nestjs/common';
import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.filter';
import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApiKeyService } from 'src/engine/core-modules/api-key/api-key.service';
import { CreateApiKeyDTO } from 'src/engine/core-modules/api-key/dtos/create-api-key.dto';
import { UpdateApiKeyDTO } from 'src/engine/core-modules/api-key/dtos/update-api-key.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
/**
* rest/apiKeys is deprecated, use rest/metadata/apiKeys instead
* rest/apiKeys will be removed in the future
*/
@Controller(['rest/apiKeys', 'rest/metadata/apiKeys'])
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
@UseFilters(RestApiExceptionFilter)
export class ApiKeyController {
constructor(private readonly apiKeyService: ApiKeyService) {}
@Get()
async findAll(@AuthWorkspace() workspace: Workspace): Promise<ApiKey[]> {
return this.apiKeyService.findActiveByWorkspaceId(workspace.id);
}
@Get(':id')
async findOne(
@Param('id') id: string,
@AuthWorkspace() workspace: Workspace,
): Promise<ApiKey | null> {
return this.apiKeyService.findById(id, workspace.id);
}
@Post()
async create(
@Body() createApiKeyDto: CreateApiKeyDTO,
@AuthWorkspace() workspace: Workspace,
): Promise<ApiKey> {
return this.apiKeyService.create({
name: createApiKeyDto.name,
expiresAt: new Date(createApiKeyDto.expiresAt),
revokedAt: createApiKeyDto.revokedAt
? new Date(createApiKeyDto.revokedAt)
: undefined,
workspaceId: workspace.id,
});
}
@Patch(':id')
async update(
@Param('id') id: string,
@Body() updateApiKeyDto: UpdateApiKeyDTO,
@AuthWorkspace() workspace: Workspace,
): Promise<ApiKey | null> {
const updateData: Partial<ApiKey> = {};
if (updateApiKeyDto.name !== undefined)
updateData.name = updateApiKeyDto.name;
if (updateApiKeyDto.expiresAt !== undefined)
updateData.expiresAt = new Date(updateApiKeyDto.expiresAt);
if (updateApiKeyDto.revokedAt !== undefined) {
updateData.revokedAt = updateApiKeyDto.revokedAt
? new Date(updateApiKeyDto.revokedAt)
: undefined;
}
return this.apiKeyService.update(id, workspace.id, updateData);
}
@Delete(':id')
async remove(
@Param('id') id: string,
@AuthWorkspace() workspace: Workspace,
): Promise<ApiKey | null> {
return this.apiKeyService.revoke(id, workspace.id);
}
}

View File

@ -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],
})

View File

@ -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();
});

View File

@ -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) => {

View File

@ -0,0 +1,78 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
UseFilters,
UseGuards,
} from '@nestjs/common';
import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.filter';
import { CreateWebhookDTO } from 'src/engine/core-modules/webhook/dtos/create-webhook.dto';
import { UpdateWebhookDTO } from 'src/engine/core-modules/webhook/dtos/update-webhook.dto';
import { Webhook } from 'src/engine/core-modules/webhook/webhook.entity';
import { WebhookService } from 'src/engine/core-modules/webhook/webhook.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
/**
* rest/webhooks is deprecated, use rest/metadata/webhooks instead
* rest/webhooks will be removed in the future
*/
@Controller(['rest/webhooks', 'rest/metadata/webhooks'])
@UseGuards(JwtAuthGuard, WorkspaceAuthGuard)
@UseFilters(RestApiExceptionFilter)
export class WebhookController {
constructor(private readonly webhookService: WebhookService) {}
@Get()
async findAll(@AuthWorkspace() workspace: Workspace): Promise<Webhook[]> {
return this.webhookService.findByWorkspaceId(workspace.id);
}
@Get(':id')
async findOne(
@Param('id') id: string,
@AuthWorkspace() workspace: Workspace,
): Promise<Webhook | null> {
return this.webhookService.findById(id, workspace.id);
}
@Post()
async create(
@Body() createWebhookDto: CreateWebhookDTO,
@AuthWorkspace() workspace: Workspace,
): Promise<Webhook> {
return this.webhookService.create({
targetUrl: createWebhookDto.targetUrl,
operations: createWebhookDto.operations || ['*.*'],
description: createWebhookDto.description,
secret: createWebhookDto.secret,
workspaceId: workspace.id,
});
}
@Patch(':id')
async update(
@Param('id') id: string,
@Body() updateWebhookDto: UpdateWebhookDTO,
@AuthWorkspace() workspace: Workspace,
): Promise<Webhook | null> {
return this.webhookService.update(id, workspace.id, updateWebhookDto);
}
@Delete(':id')
async remove(
@Param('id') id: string,
@AuthWorkspace() workspace: Workspace,
): Promise<boolean> {
const result = await this.webhookService.delete(id, workspace.id);
return result !== null;
}
}

View File

@ -1,13 +1,22 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Webhook } from './webhook.entity';
import { WebhookResolver } from './webhook.resolver';
import { WebhookService } from './webhook.service';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { Webhook } from 'src/engine/core-modules/webhook/webhook.entity';
import { WebhookResolver } from 'src/engine/core-modules/webhook/webhook.resolver';
import { WebhookService } from 'src/engine/core-modules/webhook/webhook.service';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WebhookController } from './controllers/webhook.controller';
@Module({
imports: [TypeOrmModule.forFeature([Webhook], 'core')],
imports: [
TypeOrmModule.forFeature([Webhook], 'core'),
AuthModule,
WorkspaceCacheStorageModule,
],
providers: [WebhookService, WebhookResolver],
controllers: [WebhookController],
exports: [WebhookService, TypeOrmModule],
})
export class WebhookModule {}

View File

@ -5,7 +5,7 @@ import { TypedReflect } from 'src/utils/typed-reflect';
export interface WorkspaceGateOptions {
featureFlag: string;
excludeFromDatabase?: boolean;
excludeFromGraphQL?: boolean;
excludeFromWorkspaceApi?: boolean;
}
export function WorkspaceGate(options: WorkspaceGateOptions) {
@ -21,7 +21,7 @@ export function WorkspaceGate(options: WorkspaceGateOptions) {
const gateOptions = {
featureFlag: options.featureFlag,
excludeFromDatabase: options.excludeFromDatabase ?? true,
excludeFromGraphQL: options.excludeFromGraphQL ?? true,
excludeFromWorkspaceApi: options.excludeFromWorkspaceApi ?? true,
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -1,5 +1,5 @@
export interface Gate {
featureFlag: string;
excludeFromDatabase?: boolean;
excludeFromGraphQL?: boolean;
excludeFromWorkspaceApi?: boolean;
}

View File

@ -1,6 +1,6 @@
import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface';
export type GateContext = 'database' | 'graphql';
export type GateContext = 'database' | 'workspaceApi';
export const isGatedAndNotEnabled = (
gate: Gate | undefined,
@ -19,9 +19,9 @@ export const isGatedAndNotEnabled = (
return false; // Not gated for database
}
break;
case 'graphql':
if (gate.excludeFromGraphQL === false) {
return false; // Not gated for GraphQL
case 'workspaceApi':
if (gate.excludeFromWorkspaceApi === false) {
return false; // Not gated for workspace API
}
break;
}

View File

@ -0,0 +1,23 @@
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
export const shouldExcludeFromWorkspaceApi = (
objectMetadataItem: { standardId?: string | null | undefined },
standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[],
workspaceFeatureFlagsMap: Record<string, boolean>,
): boolean => {
const entityMetadata = standardObjectMetadataDefinitions
.map((entity) => metadataArgsStorage.filterEntities(entity))
.find((meta) => meta?.standardId === objectMetadataItem.standardId);
if (!entityMetadata) {
return false; // Don't exclude non-workspace entities
}
return isGatedAndNotEnabled(
entityMetadata?.gate,
workspaceFeatureFlagsMap,
'workspaceApi',
);
};

View File

@ -24,7 +24,7 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync
@WorkspaceGate({
featureFlag: 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED',
excludeFromDatabase: false,
excludeFromGraphQL: true,
excludeFromWorkspaceApi: true,
})
export class ApiKeyWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceField({

View File

@ -25,7 +25,7 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync
@WorkspaceGate({
featureFlag: 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED',
excludeFromDatabase: false,
excludeFromGraphQL: true,
excludeFromWorkspaceApi: true,
})
export class WebhookWorkspaceEntity extends BaseWorkspaceEntity {
@WorkspaceField({