Generate GQL schema based on applicationId (#17860)
## Add application-scoped GraphQL schema generation When an application token is used to authenticate, the `/graphql` schema is now dynamically filtered to only include entities belonging to that application (plus the Twenty Standard Application). This enables third-party applications and the SDK to introspect a schema that is relevant to their scope, rather than seeing the full workspace schema with all custom objects. ### Changes - **New `generateApplicationToken` mutation** on the `/metadata` endpoint, allowing callers to exchange an API key for an application-scoped JWT token - **Schema filtering by application** in `WorkspaceSchemaFactory` — when `request.application` is present (from an application token), flat entity maps are filtered by `[appId, standardAppId]` before schema generation - **Per-app caching** — both the Yoga in-memory cache and Redis cache now include the `appId` in their keys to avoid serving wrong schemas - **Consolidated `getSubFlatEntityMapsByApplicationIdsOrThrow`** — unified the single-ID and multi-ID filtering utilities into one - **Integration tests** covering token generation (admin + API key auth) and schema introspection filtering (standard app token excludes custom objects) Schema generated on seeds with applicationToken (see that pets is missing) <img width="782" height="994" alt="image" src="https://github.com/user-attachments/assets/82510031-0965-435d-bc26-77c9f5d74e1f" />
This commit is contained in:
+13
-7
@@ -3,22 +3,28 @@ import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/typ
|
||||
import { findFlatEntitiesByApplicationId } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entities-by-application-id.util';
|
||||
import { getSubFlatEntityByIdsMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-by-ids-maps-or-throw.util';
|
||||
|
||||
export const getSubFlatEntityMapsByApplicationIdOrThrow = <
|
||||
export const getSubFlatEntityMapsByApplicationIdsOrThrow = <
|
||||
T extends SyncableFlatEntity,
|
||||
>({
|
||||
applicationId,
|
||||
applicationIds,
|
||||
flatEntityMaps,
|
||||
}: {
|
||||
applicationId: string;
|
||||
applicationIds: string[];
|
||||
flatEntityMaps: FlatEntityMaps<T>;
|
||||
}) => {
|
||||
const allApplicationFlatEntity = findFlatEntitiesByApplicationId({
|
||||
applicationId,
|
||||
flatEntityMaps,
|
||||
const allFlatEntityIds = applicationIds.flatMap((applicationId) => {
|
||||
const entities = findFlatEntitiesByApplicationId({
|
||||
applicationId,
|
||||
flatEntityMaps,
|
||||
});
|
||||
|
||||
return entities.map((entity) => entity.id);
|
||||
});
|
||||
|
||||
const uniqueFlatEntityIds = [...new Set(allFlatEntityIds)];
|
||||
|
||||
return getSubFlatEntityByIdsMapsOrThrow({
|
||||
flatEntityIds: allApplicationFlatEntity.map((flatEntity) => flatEntity.id),
|
||||
flatEntityIds: uniqueFlatEntityIds,
|
||||
flatEntityMaps,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user