import { Injectable , Logger } from '@nestjs/common' ; import { GqlOptionsFactory , GqlModuleOptions } from '@nestjs/graphql' ; import { MemcachedCache } from 'apollo-server-cache-memcached' ; import { PubSub } from 'graphql-subscriptions' ; import { GraphQLExtension , AuthenticationError } from 'apollo-server-core' ; import { MockList } from 'graphql-tools' ; import GraphQLJSON , { GraphQLJSONObject } from 'graphql-type-json' ; import * as depthLimit from 'graphql-depth-limit' ; import { getMongoRepository } from 'typeorm' ; import * as chalk from 'chalk' ; import schemaDirectives from './schemaDirectives' ; import directiveResolvers from './directiveResolvers' ; import { verifyToken } from '@auth' ; import { User } from '@entities' ; import { NODE_ENV , PRIMARY_COLOR , END_POINT , FE_URL , GRAPHQL_DEPTH_LIMIT , ACCESS_TOKEN } from '@environments' ; const pubsub = new PubSub ( ) ; @ Injectable ( ) export class GraphqlService implements GqlOptionsFactory { async createGqlOptions ( ) { return { typePaths : [ './**/*.graphql' ] , resolvers : { JSON : GraphQLJSON , JSONObject : GraphQLJSONObject } , mocks : NODE_ENV === 'testing' && { Query : ( ) => ( { users : ( ) => new MockList ( [ 2 , 6 ] ) } ) } , resolverValidationOptions : { requireResolversForResolveType : false } , path : `/${ END_POINT ! }` , cors : NODE_ENV === 'production' ? { origin : FE_URL ! , credentials : true } : true , bodyParserConfig : { limit : '50mb' } , onHealthCheck : ( ) => { return new Promise ( ( resolve , reject ) => { if ( true ) { resolve ( ) ; } else { reject ( ) ; } } ) ; } , schemaDirectives , directiveResolvers , validationRules : [ depthLimit ( GRAPHQL_DEPTH_LIMIT ! , { ignore : [ /_trusted$/ , 'idontcare' ] } , depths => { if ( depths [ '' ] === GRAPHQL_DEPTH_LIMIT ! - 1 ) { Logger . warn ( `⚠️  You can only descend ${ chalk . hex ( PRIMARY_COLOR ! ) . bold ( `${ GRAPHQL_DEPTH_LIMIT ! }` ) } levels.` , 'GraphQL' , false ) ; } } ) ] , introspection : true , playground : NODE_ENV !== 'production' && { settings : { 'editor.cursorShape' : 'underline' , 'editor.fontFamily' : `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace` , 'editor.fontSize' : 14 , 'editor.reuseHeaders' : true , 'editor.theme' : 'dark' , 'general.betaUpdates' : true , 'queryPlan.hideQueryPlanResponse' : false , 'request.credentials' : 'include' , 'tracing.hideTracingResponse' : false } } , tracing : NODE_ENV !== 'production' , cacheControl : NODE_ENV === 'production' && { defaultMaxAge : 5 , stripFormattedExtensions : false , calculateHttpHeaders : false } , context : async ( { req , res , connection } ) => { if ( connection ) { const { currentUser } = connection . context ; return { pubsub , currentUser } ; } let currentUser ; const token = req . headers [ ACCESS_TOKEN ! ] || '' ; if ( token ) { currentUser = await verifyToken ( token , 'accessToken' ) ; } return { req , res , pubsub , currentUser , trackErrors ( errors ) { } } ; } , formatError : error => { return { message : error . message , code : error . extensions && error . extensions . code , locations : error . locations , path : error . path } ; } , formatResponse : response => { return response ; } , subscriptions : { path : `/${ END_POINT ! }` , keepAlive : 1000 , onConnect : async ( connectionParams , webSocket , context ) => { NODE_ENV !== 'production' && Logger . debug ( `🔗  Connected to websocket` , 'GraphQL' ) ; let currentUser ; const token = connectionParams [ ACCESS_TOKEN ! ] ; if ( token ) { currentUser = await verifyToken ( token , 'accessToken' ) ; await getMongoRepository ( User ) . updateOne ( { _id : currentUser . _id } , { $set : { isOnline : true } } , { upsert : true } ) ; return { currentUser } ; } throw new AuthenticationError ( 'Authentication token is invalid, please try again.' ) ; } , onDisconnect : async ( webSocket , context ) => { NODE_ENV !== 'production' && Logger . error ( `❌  Disconnected to websocket` , '' , 'GraphQL' , false ) ; const { initPromise } = context ; const { currentUser } = await initPromise ; await getMongoRepository ( User ) . updateOne ( { _id : currentUser . _id } , { $set : { isOnline : false } } , { upsert : true } ) ; } } , persistedQueries : { cache : new MemcachedCache ( [ 'memcached-server-1' , 'memcached-server-2' , 'memcached-server-3' ] , { retries : 10 , retry : 10000 } ) } , installSubscriptionHandlers : true , uploads : { maxFieldSize : 2 , maxFileSize : 20 , maxFiles : 5 } } ; } }