From ab95437065dddd4f7c78fb9dd100e02688fac515 Mon Sep 17 00:00:00 2001 From: Weiko Date: Tue, 30 Dec 2025 15:16:57 +0100 Subject: [PATCH] Create query runner context from request context instead of schema builder internal context (#16858) ## Context This PR refactors how authentication context is handled in the GraphQL resolver layer. Previously, the auth context was baked into the schema builder context at schema creation time. Now, the auth context is extracted from the request at query execution time, enabling better schema caching. ## Implementation - Removed user-specific data from schema cache key: The schema cache key no longer includes userId and apiKeyId, allowing the same schema to be shared across all users in a workspace - Cache key simplified from ${workspaceId}-${userId}-${apiKeyId}-${url}-${cacheVersion} to ${workspaceId}-${url}-${cacheVersion} - Removed authContext from WorkspaceSchemaBuilderContext: The schema builder context is now purely about object/field metadata, not about who is accessing it - Created createQueryRunnerContext utility: A new helper that combines the schema builder context with the auth context from the actual request - Updated all resolver factories: All 15 resolver factories now use createQueryRunnerContext to build the full context needed by query runners at execution time ## Benefits - Improved cache efficiency: GraphQL schemas are now cached per workspace rather than per user, significantly reducing memory usage and schema regeneration - Cleaner separation of concerns: Schema building (metadata-driven) is now separate from query execution (auth-driven) - Fresh auth context: Auth context is always retrieved from the current request, ensuring it reflects the latest state - Reduced complexity: Simplified the schema creation path by removing unnecessary auth checks ## Next steps - Introduce a hash in redis and expose it in the req object so the yoga patch can access the hash and use it during its own cache key computation. This way we will be able to invalidate the local cache in yoga graphql schema generation without restarting pods. --- .../patches/@graphql-yoga+nestjs+2.1.0.patch | 56 ++++++++----------- .../graphql-config/graphql-config.service.ts | 33 +++-------- .../factories/create-many-resolver.factory.ts | 18 ++++-- .../factories/create-one-resolver.factory.ts | 18 ++++-- .../factories/delete-many-resolver.factory.ts | 18 ++++-- .../factories/delete-one-resolver.factory.ts | 18 ++++-- .../destroy-many-resolver.factory.ts | 18 ++++-- .../factories/destroy-one-resolver.factory.ts | 18 ++++-- .../find-duplicates-resolver.factory.ts | 18 ++++-- .../factories/find-many-resolver.factory.ts | 18 ++++-- .../factories/find-one-resolver.factory.ts | 18 ++++-- .../factories/group-by-resolver.factory.ts | 18 ++++-- .../factories/merge-many-resolver.factory.ts | 18 ++++-- .../restore-many-resolver.factory.ts | 18 ++++-- .../factories/restore-one-resolver.factory.ts | 18 ++++-- .../factories/update-many-resolver.factory.ts | 18 ++++-- .../factories/update-one-resolver.factory.ts | 18 ++++-- .../utils/create-query-runner-context.util.ts | 37 ++++++++++++ .../workspace-resolver.factory.ts | 17 ------ ...kspace-schema-builder-context.interface.ts | 2 - .../api/graphql/workspace-schema.factory.ts | 44 +++++---------- yarn.lock | 4 +- 22 files changed, 264 insertions(+), 199 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util.ts diff --git a/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch b/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch index 7c928da504..5479588335 100644 --- a/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch +++ b/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch @@ -1,5 +1,5 @@ diff --git a/dist/cjs/index.js b/dist/cjs/index.js -index 1684394..32602b3 100644 +index 1684394..dd0773d 100644 --- a/dist/cjs/index.js +++ b/dist/cjs/index.js @@ -3,10 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true }); @@ -26,18 +26,16 @@ index 1684394..32602b3 100644 const app = this.httpAdapterHost.httpAdapter.getInstance(); preStartHook?.(app); // nest's logger doesnt have the info method -@@ -42,6 +46,47 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { +@@ -42,6 +46,45 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { } const yoga = (0, graphql_yoga_1.createYoga)({ ...options, + schema: async (request) => { + const workspaceId = request.req.workspace?.id ?? 'anonymous' + const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const workspaceUserId = request.req.user?.id ?? 'anonymous' -+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key' + const url = request.req.baseUrl + -+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}` ++ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}` + + if(this.schemaCache.has(cacheKey)) { + return this.schemaCache.get(cacheKey) @@ -62,7 +60,7 @@ index 1684394..32602b3 100644 + }); + + for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${workspaceUserId}-${url}-`)) { ++ if (key.startsWith(`${workspaceId}-${url}-`)) { + this.schemaCache.delete(key); + } + } @@ -74,7 +72,7 @@ index 1684394..32602b3 100644 graphqlEndpoint: options.path, // disable logging by default // however, if `true` use nest logger -@@ -54,11 +98,52 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { +@@ -54,11 +97,50 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { this.yoga = yoga; app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res })); } @@ -87,11 +85,9 @@ index 1684394..32602b3 100644 + schema: async (request) => { + const workspaceId = request.req.workspace?.id ?? 'anonymous' + const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const workspaceUserId = request.req.user?.id ?? 'anonymous' -+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key' + const url = request.req.baseUrl + -+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}` ++ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}` + + if(this.schemaCache.has(cacheKey)) { + return this.schemaCache.get(cacheKey) @@ -116,7 +112,7 @@ index 1684394..32602b3 100644 + }); + + for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${workspaceUserId}-${url}-`)) { ++ if (key.startsWith(`${workspaceId}-${url}-`)) { + this.schemaCache.delete(key); + } + } @@ -129,7 +125,7 @@ index 1684394..32602b3 100644 // disable logging by default // however, if `true` use fastify logger diff --git a/dist/esm/index.js b/dist/esm/index.js -index 7068c51..b8cbf9e 100644 +index 7068c51..8494b69 100644 --- a/dist/esm/index.js +++ b/dist/esm/index.js @@ -1,9 +1,13 @@ @@ -158,18 +154,16 @@ index 7068c51..b8cbf9e 100644 const app = this.httpAdapterHost.httpAdapter.getInstance(); preStartHook?.(app); // nest's logger doesnt have the info method -@@ -39,6 +43,47 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { +@@ -39,6 +43,45 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { } const yoga = createYoga({ ...options, + schema: async (request) => { + const workspaceId = request.req.workspace?.id ?? 'anonymous' + const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const workspaceUserId = request.req.user?.id ?? 'anonymous' -+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key' + const url = request.req.baseUrl + -+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}` ++ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}` + + if (this.schemaCache.has(cacheKey)) { + return this.schemaCache.get(cacheKey) @@ -194,7 +188,7 @@ index 7068c51..b8cbf9e 100644 + }); + + for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${workspaceUserId}-${url}-`)) { ++ if (key.startsWith(`${workspaceId}-${url}-`)) { + this.schemaCache.delete(key); + } + } @@ -206,7 +200,7 @@ index 7068c51..b8cbf9e 100644 graphqlEndpoint: options.path, // disable logging by default // however, if `true` use nest logger -@@ -51,11 +95,52 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { +@@ -51,11 +94,50 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { this.yoga = yoga; app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res })); } @@ -219,11 +213,9 @@ index 7068c51..b8cbf9e 100644 + schema: async (request) => { + const workspaceId = request.req.workspace?.id ?? 'anonymous' + const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const workspaceUserId = request.req.user?.id ?? 'anonymous' -+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key' + const url = request.req.baseUrl + -+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}` ++ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}` + + if (this.schemaCache.has(cacheKey)) { + return this.schemaCache.get(cacheKey) @@ -248,7 +240,7 @@ index 7068c51..b8cbf9e 100644 + }); + + for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${workspaceUserId}-${url}-`)) { ++ if (key.startsWith(`${workspaceId}-${url}-`)) { + this.schemaCache.delete(key); + } + } @@ -338,7 +330,7 @@ index 2c6a965..fd86dac 100644 }): void; subscriptionWithFilter(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise>; diff --git a/src/index.ts b/src/index.ts -index ce142f6..10e17d2 100644 +index ce142f6..d3e7bab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ @@ -413,18 +405,16 @@ index ce142f6..10e17d2 100644 { preStartHook }: { preStartHook?: (app: Express) => void } = {}, ) { const app: Express = this.httpAdapterHost.httpAdapter.getInstance(); -@@ -98,6 +109,41 @@ export abstract class AbstractYogaDriver< +@@ -98,6 +109,39 @@ export abstract class AbstractYogaDriver< const yoga = createYoga>({ ...options, + schema: async request => { + const workspaceId = request.req.workspace.id + const workspaceCacheVersion = request.req.workspaceMetadataVersion -+ const workspaceUserId = request.req.user?.id ?? 'anonymous' -+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key' + const url = request.req.baseUrl + -+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}` ++ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}` + + if (this.schemaCache.has(cacheKey)) { + return this.schemaCache.get(cacheKey) @@ -455,7 +445,7 @@ index ce142f6..10e17d2 100644 graphqlEndpoint: options.path, // disable logging by default // however, if `true` use nest logger -@@ -105,8 +150,8 @@ export abstract class AbstractYogaDriver< +@@ -105,8 +149,8 @@ export abstract class AbstractYogaDriver< options.logging == null ? false : options.logging @@ -466,7 +456,7 @@ index ce142f6..10e17d2 100644 }); this.yoga = yoga as YogaDriverServerInstance; -@@ -115,7 +160,7 @@ export abstract class AbstractYogaDriver< +@@ -115,7 +159,7 @@ export abstract class AbstractYogaDriver< } protected registerFastify( @@ -475,18 +465,16 @@ index ce142f6..10e17d2 100644 { preStartHook }: { preStartHook?: (app: FastifyInstance) => void } = {}, ) { const app: FastifyInstance = this.httpAdapterHost.httpAdapter.getInstance(); -@@ -124,6 +169,41 @@ export abstract class AbstractYogaDriver< +@@ -124,6 +168,39 @@ export abstract class AbstractYogaDriver< const yoga = createYoga>({ ...options, + schema: async request => { + const workspaceId = request.req.workspace.id + const workspaceCacheVersion = request.req.workspaceMetadataVersion -+ const workspaceUserId = request.req.user?.id ?? 'anonymous' -+ const apiKeyId = request.req.apiKey?.id ?? 'no-api-key' + const url = request.req.baseUrl + -+ const cacheKey = `${workspaceId}-${workspaceUserId}-${apiKeyId}-${url}-${workspaceCacheVersion}` ++ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}` + + if (this.schemaCache.has(cacheKey)) { + return this.schemaCache.get(cacheKey) @@ -517,7 +505,7 @@ index ce142f6..10e17d2 100644 graphqlEndpoint: options.path, // disable logging by default // however, if `true` use fastify logger -@@ -191,8 +270,8 @@ export class YogaDriver< +@@ -191,8 +268,8 @@ export class YogaDriver< const config: SubscriptionConfig = options.subscriptions === true ? { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts index abaf2db881..67b3aec8cc 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts @@ -14,11 +14,11 @@ import { type YogaInitialContext, } from 'graphql-yoga'; import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken'; +import { isDefined } from 'twenty-shared/utils'; import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface'; import { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.factory'; -import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { ApiConfig, Billing, @@ -107,31 +107,14 @@ export class GraphQLConfigService ], }, conditionalSchema: async (context) => { - let user: UserEntity | null | undefined; - let workspace: WorkspaceEntity | undefined; + const { workspace, user } = context.req; try { - const { - user, - workspace, - apiKey, - application, - workspaceMemberId, - userWorkspaceId, - } = context.req; - - if (!workspace) { + if (!isDefined(workspace)) { return new GraphQLSchema({}); } - return await this.createSchema(context, { - user, - workspace, - apiKey, - application, - workspaceMemberId, - userWorkspaceId, - }); + return await this.createSchema(context, workspace); } catch (error) { if (error instanceof UnauthorizedException) { throw new GraphQLError('Unauthenticated', { @@ -161,7 +144,7 @@ export class GraphQLConfigService throw handleExceptionAndConvertToGraphQLError( error, this.exceptionHandlerService, - user + isDefined(user) ? { id: user.id, email: user.email, @@ -169,7 +152,7 @@ export class GraphQLConfigService lastName: user.lastName, } : undefined, - workspace + isDefined(workspace) ? { id: workspace.id, displayName: workspace.displayName, @@ -197,7 +180,7 @@ export class GraphQLConfigService async createSchema( context: YogaDriverServerContext<'express'> & YogaInitialContext, - data: AuthContext, + workspace: WorkspaceEntity, ): Promise>> { // Create a new contextId for each request const contextId = ContextIdFactory.create(); @@ -216,6 +199,6 @@ export class GraphQLConfigService }, ); - return await workspaceFactory.createGraphQLSchema(data); + return await workspaceFactory.createGraphQLSchema(workspace); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts index bd1296f080..fd8f29a37c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class CreateManyResolverFactory @@ -30,26 +31,31 @@ export class CreateManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const records = await this.commonCreateManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return records.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }), diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts index 6550acccc6..12864ed51e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common- import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class CreateOneResolverFactory @@ -29,25 +30,30 @@ export class CreateOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const record = await this.commonCreateOneQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts index 608ccf5813..0b8946ad95 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class DeleteManyResolverFactory @@ -30,26 +31,31 @@ export class DeleteManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const records = await this.commonDeleteManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return records.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }), diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts index 1bc0ae1acf..d524535385 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common- import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class DeleteOneResolverFactory @@ -29,25 +30,30 @@ export class DeleteOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const record = await this.commonDeleteOneQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts index 46f57ecfc2..15486fa0ad 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/commo import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class DestroyManyResolverFactory @@ -30,26 +31,31 @@ export class DestroyManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const records = await this.commonDestroyManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return records.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }), diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts index d7afd06d55..132ac445f6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class DestroyOneResolverFactory @@ -29,25 +30,30 @@ export class DestroyOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const record = await this.commonDestroyOneQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts index 15f6192899..0bbdc0e57f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonFindDuplicatesQueryRunnerService } from 'src/engine/api/common/co import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class FindDuplicatesResolverFactory @@ -30,27 +31,32 @@ export class FindDuplicatesResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const paginatedDuplicates = await this.commonFindDuplicatesQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return paginatedDuplicates.map((duplicate) => typeORMObjectRecordsParser.createConnection({ objectRecords: duplicate.records, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: duplicate.records.length, totalCount: duplicate.totalCount, order: [{ id: OrderByDirection.AscNullsFirst }], diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts index f9b173ace9..d66bbe3f14 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonFindManyQueryRunnerService } from 'src/engine/api/common/common-q import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class FindManyResolverFactory @@ -30,9 +31,14 @@ export class FindManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const { records, @@ -42,21 +48,21 @@ export class FindManyResolverFactory selectedFieldsResult, } = await this.commonFindManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.createConnection({ objectRecords: records, objectRecordsAggregatedValues: aggregatedValues, selectedAggregatedFields: selectedFieldsResult.aggregate, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: args.first ?? args.last ?? QUERY_MAX_RECORDS, totalCount, order: args.orderBy, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts index fe8160001c..46ff8b36e0 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-qu import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class FindOneResolverFactory @@ -29,25 +30,30 @@ export class FindOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { try { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + const record = await this.commonFindOneQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/group-by-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/group-by-resolver.factory.ts index 946b216c24..1595c6d73b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/group-by-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/group-by-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-qu import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class GroupByResolverFactory @@ -30,9 +31,14 @@ export class GroupByResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + const shouldIncludeRecords = isDefined(selectedFields.edges?.node) && Object.keys(selectedFields.edges?.node).length > 0; @@ -40,20 +46,20 @@ export class GroupByResolverFactory try { const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); const results = await this.commonGroupByQueryRunnerService.execute( { ...args, selectedFields, includeRecords: shouldIncludeRecords }, - internalContext, + resolverContext, ); const formattedResults = results.map((group) => { const formattedRecords = typeORMObjectRecordsParser.createConnection({ objectRecords: group.records ?? [], - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, objectRecordsAggregatedValues: {}, selectedAggregatedFields: {}, take: group.records?.length || 0, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/merge-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/merge-many-resolver.factory.ts index 86345942d5..e48688e3df 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/merge-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/merge-many-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonMergeManyQueryRunnerService } from 'src/engine/api/common/common- import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class MergeManyResolverFactory @@ -29,25 +30,30 @@ export class MergeManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const record = await this.commonMergeManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts index 444fee1a9b..5d33bca725 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonRestoreManyQueryRunnerService } from 'src/engine/api/common/commo import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class RestoreManyResolverFactory @@ -30,26 +31,31 @@ export class RestoreManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const records = await this.commonRestoreManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return records.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }), diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts index 3bf4b686c0..2c7cccde5d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonRestoreOneQueryRunnerService } from 'src/engine/api/common/common import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class RestoreOneResolverFactory @@ -29,25 +30,30 @@ export class RestoreOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const record = await this.commonRestoreOneQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts index 90a0868826..c859601e32 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -14,6 +14,7 @@ import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class UpdateManyResolverFactory @@ -30,26 +31,31 @@ export class UpdateManyResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const records = await this.commonUpdateManyQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return records.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }), diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts index c31aef9150..c45aec5b57 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts @@ -13,6 +13,7 @@ import { CommonUpdateOneQueryRunnerService } from 'src/engine/api/common/common- import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; +import { createQueryRunnerContext } from 'src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util'; @Injectable() export class UpdateOneResolverFactory @@ -29,25 +30,30 @@ export class UpdateOneResolverFactory ): Resolver { const internalContext = context; - return async (_source, args, _context, info) => { + return async (_source, args, requestContext, info) => { const selectedFields = graphqlFields(info); + const resolverContext = createQueryRunnerContext({ + workspaceSchemaBuilderContext: internalContext, + request: requestContext.req, + }); + try { const record = await this.commonUpdateOneQueryRunnerService.execute( { ...args, selectedFields }, - internalContext, + resolverContext, ); const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper( - internalContext.flatObjectMetadataMaps, - internalContext.flatFieldMetadataMaps, - internalContext.objectIdByNameSingular, + resolverContext.flatObjectMetadataMaps, + resolverContext.flatFieldMetadataMaps, + resolverContext.objectIdByNameSingular, ); return typeORMObjectRecordsParser.processRecord({ objectRecord: record, - objectName: internalContext.flatObjectMetadata.nameSingular, + objectName: resolverContext.flatObjectMetadata.nameSingular, take: 1, totalCount: 1, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util.ts new file mode 100644 index 0000000000..5cca2656c9 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/utils/create-query-runner-context.util.ts @@ -0,0 +1,37 @@ +import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; + +import { type CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type'; +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; + +export const createQueryRunnerContext = ({ + workspaceSchemaBuilderContext, + request, +}: { + workspaceSchemaBuilderContext: WorkspaceSchemaBuilderContext; + request: AuthContext; +}): CommonBaseQueryRunnerContext => { + const { + user, + workspace, + apiKey, + workspaceMemberId, + userWorkspaceId, + userWorkspace, + authProvider, + impersonationContext, + } = request; + + return { + ...workspaceSchemaBuilderContext, + authContext: { + user, + workspace, + apiKey, + workspaceMemberId, + userWorkspaceId, + userWorkspace, + authProvider, + impersonationContext, + }, + }; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index 0db00351d5..d206f3a1f7 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -12,11 +12,6 @@ import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-res import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.service'; -import { - AuthException, - AuthExceptionCode, -} from 'src/engine/core-modules/auth/auth.exception'; -import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -59,7 +54,6 @@ export class WorkspaceResolverFactory { ) {} async create( - authContext: AuthContext, flatObjectMetadataMaps: FlatEntityMaps, flatFieldMetadataMaps: FlatEntityMaps, objectIdByNameSingular: Record, @@ -90,15 +84,6 @@ export class WorkspaceResolverFactory { Mutation: {}, }; - const workspaceId = authContext.workspace?.id; - - if (!workspaceId) { - throw new AuthException( - 'Unauthenticated', - AuthExceptionCode.UNAUTHENTICATED, - ); - } - for (const flatObjectMetadata of Object.values( flatObjectMetadataMaps.byId, ).filter(isDefined)) { @@ -125,7 +110,6 @@ export class WorkspaceResolverFactory { ) { // @ts-expect-error legacy noImplicitAny resolvers.Query[resolverName] = resolverFactory.create({ - authContext, flatObjectMetadata, flatObjectMetadataMaps, flatFieldMetadataMaps, @@ -157,7 +141,6 @@ export class WorkspaceResolverFactory { ) { // @ts-expect-error legacy noImplicitAny resolvers.Mutation[resolverName] = resolverFactory.create({ - authContext, flatObjectMetadata, flatObjectMetadataMaps, flatFieldMetadataMaps, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts index 1b7737ac38..10f8aa2cf2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts @@ -1,10 +1,8 @@ -import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; export interface WorkspaceSchemaBuilderContext { - authContext: AuthContext; flatObjectMetadata: FlatObjectMetadata; flatObjectMetadataMaps: FlatEntityMaps; flatFieldMetadataMaps: FlatEntityMaps; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 988dd0890a..5191edfa65 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -9,11 +9,7 @@ import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars- import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories'; import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory'; import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory'; -import { - AuthException, - AuthExceptionCode, -} from 'src/engine/core-modules/auth/auth.exception'; -import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { FlatEntityMapsException, @@ -34,33 +30,22 @@ export class WorkspaceSchemaFactory { private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} - async createGraphQLSchema(authContext: AuthContext): Promise { - if (!authContext.workspace?.id) { - return new GraphQLSchema({}); - } - + async createGraphQLSchema( + workspace: WorkspaceEntity, + ): Promise { const dataSourcesMetadata = await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId( - authContext.workspace.id, + workspace.id, ); if (!dataSourcesMetadata || dataSourcesMetadata.length === 0) { return new GraphQLSchema({}); } - const workspaceId = authContext.workspace.id; - - if (!workspaceId) { - throw new AuthException( - 'Unauthenticated', - AuthExceptionCode.UNAUTHENTICATED, - ); - } - const { flatObjectMetadataMaps, flatFieldMetadataMaps, flatIndexMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { - workspaceId, + workspaceId: workspace.id, flatMapsKeys: [ 'flatObjectMetadataMaps', 'flatFieldMetadataMaps', @@ -84,12 +69,14 @@ export class WorkspaceSchemaFactory { } let metadataVersion = - await this.workspaceCacheStorageService.getMetadataVersion(workspaceId); + await this.workspaceCacheStorageService.getMetadataVersion(workspace.id); if (!isDefined(metadataVersion)) { - metadataVersion = authContext.workspace.metadataVersion ?? 0; + metadataVersion = isDefined(workspace.metadataVersion) + ? workspace.metadataVersion + : 0; await this.workspaceCacheStorageService.setMetadataVersion( - workspaceId, + workspace.id, metadataVersion, ); } @@ -99,12 +86,12 @@ export class WorkspaceSchemaFactory { ); let typeDefs = await this.workspaceCacheStorageService.getGraphQLTypeDefs( - authContext.workspace.id, + workspace.id, metadataVersion, ); let usedScalarNames = await this.workspaceCacheStorageService.getGraphQLUsedScalarNames( - authContext.workspace.id, + workspace.id, metadataVersion, ); @@ -121,19 +108,18 @@ export class WorkspaceSchemaFactory { typeDefs = printSchema(autoGeneratedSchema); await this.workspaceCacheStorageService.setGraphQLTypeDefs( - authContext.workspace.id, + workspace.id, metadataVersion, typeDefs, ); await this.workspaceCacheStorageService.setGraphQLUsedScalarNames( - authContext.workspace.id, + workspace.id, metadataVersion, usedScalarNames, ); } const autoGeneratedResolvers = await this.workspaceResolverFactory.create( - authContext, flatObjectMetadataMaps, flatFieldMetadataMaps, idByNameSingular, diff --git a/yarn.lock b/yarn.lock index 7a3c53b0b5..92a2d08fa8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7659,14 +7659,14 @@ __metadata: "@graphql-yoga/nestjs@patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server": version: 2.1.0 - resolution: "@graphql-yoga/nestjs@patch:@graphql-yoga/nestjs@npm%3A2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch::version=2.1.0&hash=971f26&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server" + resolution: "@graphql-yoga/nestjs@patch:@graphql-yoga/nestjs@npm%3A2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch::version=2.1.0&hash=d6801e&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server" peerDependencies: "@nestjs/common": ^10.0.0 "@nestjs/core": ^10.0.0 "@nestjs/graphql": ^12.0.0 graphql: ^15.0.0 || ^16.0.0 graphql-yoga: ^4.0.4 - checksum: 10c0/68ebaf195c93a6d31e22f91e5474cfb51675eac44a6c1a2ef7c856539e704dd9e7f8ea7cfd930a0ad37894edbcab6502b642f202d9f31f8f38b36c7d4d5add43 + checksum: 10c0/929da1f7265003cb5f43291d6db96e752bd2ac5d81e21fb3af9b245e54e732f5862789d7e5941e893d85255919b4c6a5a37adb16ef6594fab606e8eec742891c languageName: node linkType: hard