diff --git a/packages/plugins/graphql/config/functions/bootstrap.js b/packages/plugins/graphql/config/functions/bootstrap.js new file mode 100644 index 0000000000..7438586e1f --- /dev/null +++ b/packages/plugins/graphql/config/functions/bootstrap.js @@ -0,0 +1,90 @@ +'use strict'; + +const _ = require('lodash'); +const { ApolloServer } = require('apollo-server-koa'); +const depthLimit = require('graphql-depth-limit'); +const { graphqlUploadKoa } = require('graphql-upload'); + +module.exports = ({ strapi }) => { + const schema = strapi.plugins.graphql.services.schema(strapi).generateContentAPISchema(); + + if (_.isEmpty(schema)) { + strapi.log.warn('The GraphQL schema has not been generated because it is empty'); + + return; + } + + const config = _.get(strapi.plugins.graphql, 'config', {}); + + // TODO: Remove these deprecated options in favor of `apolloServer` in the next major version + const deprecatedApolloServerConfig = { + tracing: _.get(config, 'tracing', false), + introspection: _.get(config, 'introspection', true), + engine: _.get(config, 'engine', false), + }; + + if (['tracing', 'introspection', 'engine'].some(key => _.has(config, key))) { + strapi.log.warn( + 'The `tracing`, `introspection` and `engine` options are deprecated in favor of the `apolloServer` object and they will be removed in the next major version.' + ); + } + + const apolloServerConfig = _.get(config, 'apolloServer', {}); + + const serverParams = { + schema, + uploads: false, + context: ({ ctx }) => { + // Initialize loaders for this request. + // TODO: set loaders in the context not globally + + strapi.plugins.graphql.services.old['data-loaders'].initializeLoader(); + + return { + context: ctx, + }; + }, + formatError: err => { + const formatError = _.get(config, 'formatError', null); + + return typeof formatError === 'function' ? formatError(err) : err; + }, + validationRules: [depthLimit(config.depthLimit)], + playground: false, + cors: false, + bodyParserConfig: true, + // TODO: Remove these deprecated options in favor of `apolloServerConfig` in the next major version + ...deprecatedApolloServerConfig, + ...apolloServerConfig, + }; + + // Disable GraphQL Playground in production environment. + if (strapi.config.environment !== 'production' || config.playgroundAlways) { + serverParams.playground = { + endpoint: `${strapi.config.server.url}${config.endpoint}`, + shareEnabled: config.shareEnabled, + }; + } + + const server = new ApolloServer(serverParams); + + const uploadMiddleware = graphqlUploadKoa(); + strapi.app.use((ctx, next) => { + if (ctx.path === config.endpoint) { + return uploadMiddleware(ctx, next); + } + + return next(); + }); + + server.start().then(() => { + server.applyMiddleware({ + app: strapi.app, + path: config.endpoint, + }); + }); + + strapi.plugins.graphql.destroy = async () => { + await server.stop(); + }; +}; diff --git a/packages/plugins/graphql/hooks/graphql/load-config.js b/packages/plugins/graphql/config/functions/load-config.js similarity index 100% rename from packages/plugins/graphql/hooks/graphql/load-config.js rename to packages/plugins/graphql/config/functions/load-config.js diff --git a/packages/plugins/graphql/config/functions/register.js b/packages/plugins/graphql/config/functions/register.js new file mode 100644 index 0000000000..da9ea198d2 --- /dev/null +++ b/packages/plugins/graphql/config/functions/register.js @@ -0,0 +1,82 @@ +'use strict'; + +const _ = require('lodash'); +const loadConfigs = require('./load-config'); + +const attachMetadataToResolvers = (schema, { api, plugin }) => { + const { resolver = {} } = schema; + if (_.isEmpty(resolver)) return schema; + + Object.keys(resolver).forEach(type => { + if (!_.isPlainObject(resolver[type])) return; + + Object.keys(resolver[type]).forEach(resolverName => { + if (!_.isPlainObject(resolver[type][resolverName])) return; + + resolver[type][resolverName]['_metadatas'] = { + api, + plugin, + }; + }); + }); + + return schema; +}; + +module.exports = async ({ strapi }) => { + const { appPath, installedPlugins } = strapi.config; + + // Try to inject this hook just after the others hooks to skip the router processing. + if (!strapi.config.get('hook.load.after')) { + strapi.config.hook.load.after = []; + } + + strapi.config.hook.load.after.push('graphql'); + // Load core utils. + + const { api, plugins, extensions } = await loadConfigs({ + appPath, + installedPlugins, + }); + + _.merge(strapi, { api, plugins }); + + // Create a merge of all the GraphQL configuration. + const apisSchemas = Object.keys(strapi.api || {}).map(key => { + const schema = _.get(strapi.api[key], 'config.schema.graphql', {}); + return attachMetadataToResolvers(schema, { api: key }); + }); + + const pluginsSchemas = Object.keys(strapi.plugins || {}).map(key => { + const schema = _.get(strapi.plugins[key], 'config.schema.graphql', {}); + return attachMetadataToResolvers(schema, { plugin: key }); + }); + + const extensionsSchemas = Object.keys(extensions || {}).map(key => { + const schema = _.get(extensions[key], 'config.schema.graphql', {}); + return attachMetadataToResolvers(schema, { plugin: key }); + }); + + const baseSchema = mergeSchemas([...pluginsSchemas, ...extensionsSchemas, ...apisSchemas]); + + // save the final schema in the plugin's config + _.set(strapi.plugins.graphql, 'config._schema.graphql', baseSchema); +}; + +/** + * Merges a list of schemas + * @param {Array} schemas - The list of schemas to merge + */ +const mergeSchemas = schemas => { + return schemas.reduce((acc, el) => { + const { definition, query, mutation, type, resolver } = el; + + return _.merge(acc, { + definition: `${acc.definition || ''} ${definition || ''}`, + query: `${acc.query || ''} ${query || ''}`, + mutation: `${acc.mutation || ''} ${mutation || ''}`, + type, + resolver, + }); + }, {}); +}; diff --git a/packages/plugins/graphql/hooks/graphql/defaults.json b/packages/plugins/graphql/hooks/graphql/defaults.json deleted file mode 100644 index f515a839e9..0000000000 --- a/packages/plugins/graphql/hooks/graphql/defaults.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "graphql": { - "enabled": true - } -} diff --git a/packages/plugins/graphql/hooks/graphql/index.js b/packages/plugins/graphql/hooks/graphql/index.js deleted file mode 100644 index 54ad58b88a..0000000000 --- a/packages/plugins/graphql/hooks/graphql/index.js +++ /dev/null @@ -1,177 +0,0 @@ -'use strict'; - -/** - * Module dependencies - */ - -// Public node modules. -const _ = require('lodash'); -const { ApolloServer } = require('apollo-server-koa'); -const depthLimit = require('graphql-depth-limit'); -const { graphqlUploadKoa } = require('graphql-upload'); -const loadConfigs = require('./load-config'); - -const attachMetadataToResolvers = (schema, { api, plugin }) => { - const { resolver = {} } = schema; - if (_.isEmpty(resolver)) return schema; - - Object.keys(resolver).forEach(type => { - if (!_.isPlainObject(resolver[type])) return; - - Object.keys(resolver[type]).forEach(resolverName => { - if (!_.isPlainObject(resolver[type][resolverName])) return; - - resolver[type][resolverName]['_metadatas'] = { - api, - plugin, - }; - }); - }); - - return schema; -}; - -module.exports = strapi => { - const { appPath, installedPlugins } = strapi.config; - - return { - async beforeInitialize() { - // Try to inject this hook just after the others hooks to skip the router processing. - if (!strapi.config.get('hook.load.after')) { - _.set(strapi.config.hook.load, 'after', []); - } - - strapi.config.hook.load.after.push('graphql'); - // Load core utils. - - const { api, plugins, extensions } = await loadConfigs({ - appPath, - installedPlugins, - }); - _.merge(strapi, { api, plugins }); - - // Create a merge of all the GraphQL configuration. - const apisSchemas = Object.keys(strapi.api || {}).map(key => { - const schema = _.get(strapi.api[key], 'config.schema.graphql', {}); - return attachMetadataToResolvers(schema, { api: key }); - }); - - const pluginsSchemas = Object.keys(strapi.plugins || {}).map(key => { - const schema = _.get(strapi.plugins[key], 'config.schema.graphql', {}); - return attachMetadataToResolvers(schema, { plugin: key }); - }); - - const extensionsSchemas = Object.keys(extensions || {}).map(key => { - const schema = _.get(extensions[key], 'config.schema.graphql', {}); - return attachMetadataToResolvers(schema, { plugin: key }); - }); - - const baseSchema = mergeSchemas([...pluginsSchemas, ...extensionsSchemas, ...apisSchemas]); - - // save the final schema in the plugin's config - _.set(strapi.plugins.graphql, 'config._schema.graphql', baseSchema); - }, - - initialize() { - const schema = strapi.plugins.graphql.services.schema(strapi).generateContentAPISchema(); - - if (_.isEmpty(schema)) { - strapi.log.warn('The GraphQL schema has not been generated because it is empty'); - - return; - } - - const config = _.get(strapi.plugins.graphql, 'config', {}); - - // TODO: Remove these deprecated options in favor of `apolloServer` in the next major version - const deprecatedApolloServerConfig = { - tracing: _.get(config, 'tracing', false), - introspection: _.get(config, 'introspection', true), - engine: _.get(config, 'engine', false), - }; - - if (['tracing', 'introspection', 'engine'].some(key => _.has(config, key))) { - strapi.log.warn( - 'The `tracing`, `introspection` and `engine` options are deprecated in favor of the `apolloServer` object and they will be removed in the next major version.' - ); - } - - const apolloServerConfig = _.get(config, 'apolloServer', {}); - - const serverParams = { - schema, - uploads: false, - context: ({ ctx }) => { - // Initialize loaders for this request. - // TODO: set loaders in the context not globally - - strapi.plugins.graphql.services.old['data-loaders'].initializeLoader(); - - return { - context: ctx, - }; - }, - formatError: err => { - const formatError = _.get(config, 'formatError', null); - - return typeof formatError === 'function' ? formatError(err) : err; - }, - validationRules: [depthLimit(config.depthLimit)], - playground: false, - cors: false, - bodyParserConfig: true, - // TODO: Remove these deprecated options in favor of `apolloServerConfig` in the next major version - ...deprecatedApolloServerConfig, - ...apolloServerConfig, - }; - - // Disable GraphQL Playground in production environment. - if (strapi.config.environment !== 'production' || config.playgroundAlways) { - serverParams.playground = { - endpoint: `${strapi.config.server.url}${config.endpoint}`, - shareEnabled: config.shareEnabled, - }; - } - - const server = new ApolloServer(serverParams); - - const uploadMiddleware = graphqlUploadKoa(); - strapi.app.use((ctx, next) => { - if (ctx.path === config.endpoint) { - return uploadMiddleware(ctx, next); - } - - return next(); - }); - - server.start().then(() => { - server.applyMiddleware({ - app: strapi.app, - path: config.endpoint, - }); - }); - - strapi.plugins.graphql.destroy = async () => { - await server.stop(); - }; - }, - }; -}; - -/** - * Merges a list of schemas - * @param {Array} schemas - The list of schemas to merge - */ -const mergeSchemas = schemas => { - return schemas.reduce((acc, el) => { - const { definition, query, mutation, type, resolver } = el; - - return _.merge(acc, { - definition: `${acc.definition || ''} ${definition || ''}`, - query: `${acc.query || ''} ${query || ''}`, - mutation: `${acc.mutation || ''} ${mutation || ''}`, - type, - resolver, - }); - }, {}); -};