mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	Add metadatas to resolvers to know where they are created
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
		
							parent
							
								
									0c6d39297f
								
							
						
					
					
						commit
						0285c7bd96
					
				@ -24,7 +24,7 @@ module.exports = {
 | 
			
		||||
        resolver: 'application::homepage.homepage.find',
 | 
			
		||||
      },
 | 
			
		||||
      q1: {
 | 
			
		||||
        policies: ['homepage.test'],
 | 
			
		||||
        policies: ['test'],
 | 
			
		||||
        resolverOf: 'application::restaurant.restaurant.find',
 | 
			
		||||
        resolver(root, args, ctx) {
 | 
			
		||||
          return {
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
  "amountLimit": 5
 | 
			
		||||
  "amountLimit": 5,
 | 
			
		||||
  "depthLimit": 10
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,26 @@ const { ApolloServer } = require('apollo-server-koa');
 | 
			
		||||
const depthLimit = require('graphql-depth-limit');
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
@ -32,23 +52,32 @@ module.exports = strapi => {
 | 
			
		||||
      /*
 | 
			
		||||
       * Create a merge of all the GraphQL configuration.
 | 
			
		||||
       */
 | 
			
		||||
      const apisSchemas = Object.keys(strapi.api || {}).map(key =>
 | 
			
		||||
        _.get(strapi.api[key], 'config.schema.graphql', {})
 | 
			
		||||
      );
 | 
			
		||||
      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 =>
 | 
			
		||||
        _.get(strapi.plugins[key], 'config.schema.graphql', {})
 | 
			
		||||
      );
 | 
			
		||||
      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 =>
 | 
			
		||||
        _.get(extensions[key], 'config.schema.graphql', {})
 | 
			
		||||
      );
 | 
			
		||||
      const extensionsSchemas = Object.keys(extensions || {}).map(key => {
 | 
			
		||||
        const schema = _.get(extensions[key], 'config.schema.graphql', {});
 | 
			
		||||
        return attachMetadataToResolvers(schema, { plugin: key });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const baseSchema = mergeSchemas([
 | 
			
		||||
        ...apisSchemas,
 | 
			
		||||
        ...pluginsSchemas,
 | 
			
		||||
        ...extensionsSchemas,
 | 
			
		||||
      ]);
 | 
			
		||||
 | 
			
		||||
      // save the final schema in the plugin's config
 | 
			
		||||
      _.set(
 | 
			
		||||
        strapi,
 | 
			
		||||
        ['plugins', 'graphql', 'config', '_schema', 'graphql'],
 | 
			
		||||
        mergeSchemas([...apisSchemas, ...pluginsSchemas, ...extensionsSchemas])
 | 
			
		||||
        baseSchema
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ const loadPluginsGraphqlConfig = async installedPlugins => {
 | 
			
		||||
      pluginDir,
 | 
			
		||||
      'config/*.graphql?(.js)'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    _.set(root, ['plugins', pluginName], result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,6 @@
 | 
			
		||||
const _ = require('lodash');
 | 
			
		||||
const pluralize = require('pluralize');
 | 
			
		||||
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
 | 
			
		||||
const policyUtils = require('strapi-utils').policy;
 | 
			
		||||
const compose = require('koa-compose');
 | 
			
		||||
 | 
			
		||||
const Schema = require('./Schema.js');
 | 
			
		||||
const GraphQLQuery = require('./Query.js');
 | 
			
		||||
@ -204,14 +202,15 @@ const preProcessGroupByData = function({ result, fieldKey, filters, model }) {
 | 
			
		||||
  const _result = _.toArray(result);
 | 
			
		||||
  return _.map(_result, value => {
 | 
			
		||||
    return {
 | 
			
		||||
      key: value._id,
 | 
			
		||||
      key: value._id.toString(),
 | 
			
		||||
      connection: () => {
 | 
			
		||||
        // filter by the grouped by value in next connection
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          ...filters,
 | 
			
		||||
          where: {
 | 
			
		||||
            ...(filters.where || {}),
 | 
			
		||||
            [fieldKey]: value._id,
 | 
			
		||||
            [fieldKey]: value._id.toString(),
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
      },
 | 
			
		||||
@ -256,7 +255,7 @@ const createGroupByFieldsResolver = function(model, fields, name) {
 | 
			
		||||
      filters: convertRestQueryParams(params),
 | 
			
		||||
      aggregate: true,
 | 
			
		||||
    }).group({
 | 
			
		||||
      _id: `$${fieldKey}`,
 | 
			
		||||
      _id: `$${fieldKey === 'id' ? model.primaryKey : fieldKey}`,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return preProcessGroupByData({
 | 
			
		||||
@ -476,14 +475,13 @@ const formatModelConnectionsGQL = function({
 | 
			
		||||
  fields,
 | 
			
		||||
  model,
 | 
			
		||||
  name,
 | 
			
		||||
  rootQuery,
 | 
			
		||||
  resolver,
 | 
			
		||||
  plugin,
 | 
			
		||||
}) {
 | 
			
		||||
  const { globalId } = model;
 | 
			
		||||
 | 
			
		||||
  const _schema = strapi.plugins.graphql.config._schema.graphql;
 | 
			
		||||
 | 
			
		||||
  const connectionGlobalId = `${globalId}Connection`;
 | 
			
		||||
 | 
			
		||||
  const aggregatorFormat = formatConnectionAggregator(fields, model, name);
 | 
			
		||||
  const groupByFormat = formatConnectionGroupBy(fields, model, name);
 | 
			
		||||
  const connectionFields = {
 | 
			
		||||
@ -503,16 +501,9 @@ const formatModelConnectionsGQL = function({
 | 
			
		||||
 | 
			
		||||
  const queryName = `${pluralName}Connection(sort: String, limit: Int, start: Int, where: JSON)`;
 | 
			
		||||
 | 
			
		||||
  const policiesFn = [
 | 
			
		||||
    policyUtils.globalPolicy({
 | 
			
		||||
      controller: name,
 | 
			
		||||
      action: 'find',
 | 
			
		||||
      plugin,
 | 
			
		||||
    }),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  policiesFn.push(
 | 
			
		||||
    policyUtils.get('plugins.users-permissions.permissions', plugin, name)
 | 
			
		||||
  const connectionResolver = Schema.buildQuery(
 | 
			
		||||
    `${pluralName}Connection.values`,
 | 
			
		||||
    resolver
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
@ -523,25 +514,16 @@ const formatModelConnectionsGQL = function({
 | 
			
		||||
    },
 | 
			
		||||
    resolvers: {
 | 
			
		||||
      Query: {
 | 
			
		||||
        async [`${pluralName}Connection`](obj, options, { context }) {
 | 
			
		||||
          // need to check
 | 
			
		||||
          const ctx = context.app.createContext(
 | 
			
		||||
            _.clone(context.req),
 | 
			
		||||
            _.clone(context.res)
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          await compose(policiesFn)(ctx);
 | 
			
		||||
        [`${pluralName}Connection`]: {
 | 
			
		||||
          resolverOf: resolver.resolverOf || resolver.resolver,
 | 
			
		||||
          resolver(obj, options, { context }) {
 | 
			
		||||
            return options;
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      [connectionGlobalId]: {
 | 
			
		||||
        values(obj, options, context) {
 | 
			
		||||
          // use base resolver
 | 
			
		||||
          return _.get(_schema, ['resolver', 'Query', rootQuery])(
 | 
			
		||||
            obj,
 | 
			
		||||
            obj,
 | 
			
		||||
            context
 | 
			
		||||
          );
 | 
			
		||||
        values(obj, options, gqlCtx) {
 | 
			
		||||
          return connectionResolver(obj, obj, gqlCtx);
 | 
			
		||||
        },
 | 
			
		||||
        groupBy(obj, options, context) {
 | 
			
		||||
          return obj;
 | 
			
		||||
 | 
			
		||||
@ -424,6 +424,7 @@ const buildCollectionType = model => {
 | 
			
		||||
        Query: {
 | 
			
		||||
          [singularName]: {
 | 
			
		||||
            resolver: `${model.uid}.findOne`,
 | 
			
		||||
            ..._.get(_schema, `resolver.Query.${singularName}`),
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
@ -431,7 +432,7 @@ const buildCollectionType = model => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isQueryEnabled(_schema, pluralName)) {
 | 
			
		||||
    const resolverObj = {
 | 
			
		||||
    const resolverOpt = {
 | 
			
		||||
      resolver: `${model.uid}.find`,
 | 
			
		||||
      ..._.get(_schema, `resolver.Query.${pluralName}`),
 | 
			
		||||
    };
 | 
			
		||||
@ -442,10 +443,24 @@ const buildCollectionType = model => {
 | 
			
		||||
      },
 | 
			
		||||
      resolvers: {
 | 
			
		||||
        Query: {
 | 
			
		||||
          [pluralName]: resolverObj,
 | 
			
		||||
          [pluralName]: resolverOpt,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // TODO: Add support for Graphql Aggregation in Bookshelf ORM
 | 
			
		||||
    if (model.orm === 'mongoose') {
 | 
			
		||||
      // Generation the aggregation for the given model
 | 
			
		||||
      const aggregationSchema = Aggregator.formatModelConnectionsGQL({
 | 
			
		||||
        fields: typeDefObj,
 | 
			
		||||
        model,
 | 
			
		||||
        name: modelName,
 | 
			
		||||
        resolver: resolverOpt,
 | 
			
		||||
        plugin,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      mergeSchemas(localSchema, aggregationSchema);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add model Input definition.
 | 
			
		||||
@ -458,20 +473,6 @@ const buildCollectionType = model => {
 | 
			
		||||
    mergeSchemas(localSchema, mutationScheam);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // TODO: Add support for Graphql Aggregation in Bookshelf ORM
 | 
			
		||||
  if (model.orm === 'mongoose') {
 | 
			
		||||
    // Generation the aggregation for the given model
 | 
			
		||||
    const aggregationSchema = Aggregator.formatModelConnectionsGQL({
 | 
			
		||||
      fields: typeDefObj,
 | 
			
		||||
      model,
 | 
			
		||||
      name: modelName,
 | 
			
		||||
      rootQuery: pluralName,
 | 
			
		||||
      plugin,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    mergeSchemas(localSchema, aggregationSchema);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return localSchema;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,14 +16,13 @@ const { mergeSchemas, createDefaultSchema } = require('./utils');
 | 
			
		||||
const policyUtils = require('strapi-utils').policy;
 | 
			
		||||
const compose = require('koa-compose');
 | 
			
		||||
 | 
			
		||||
const schemaBuilder = {
 | 
			
		||||
  /**
 | 
			
		||||
/**
 | 
			
		||||
 * Receive an Object and return a string which is following the GraphQL specs.
 | 
			
		||||
 *
 | 
			
		||||
 * @return String
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
  formatGQL: function(fields, description = {}, model = {}, type = 'field') {
 | 
			
		||||
const formatGQL = (fields, description = {}, model = {}, type = 'field') => {
 | 
			
		||||
  const typeFields = JSON.stringify(fields, null, 2).replace(/['",]+/g, '');
 | 
			
		||||
 | 
			
		||||
  const lines = typeFields.split('\n');
 | 
			
		||||
@ -102,15 +101,15 @@ const schemaBuilder = {
 | 
			
		||||
      return line;
 | 
			
		||||
    })
 | 
			
		||||
    .join('\n');
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieve description from variable and return a string which follow the GraphQL specs.
 | 
			
		||||
 *
 | 
			
		||||
 * @return String
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
  getDescription: (type, model = {}) => {
 | 
			
		||||
const getDescription = (type, model = {}) => {
 | 
			
		||||
  const format = '"""\n';
 | 
			
		||||
 | 
			
		||||
  const str = _.get(type, '_description') || _.get(model, 'info.description');
 | 
			
		||||
@ -120,21 +119,20 @@ const schemaBuilder = {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return '';
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
/**
 | 
			
		||||
 * Generate GraphQL schema.
 | 
			
		||||
 *
 | 
			
		||||
 * @return Schema
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
  generateSchema: function() {
 | 
			
		||||
    const shadowCRUDEnabled =
 | 
			
		||||
      strapi.plugins.graphql.config.shadowCRUD !== false;
 | 
			
		||||
const generateSchema = () => {
 | 
			
		||||
  const shadowCRUDEnabled = strapi.plugins.graphql.config.shadowCRUD !== false;
 | 
			
		||||
 | 
			
		||||
  // Generate type definition and query/mutation for models.
 | 
			
		||||
  const shadowCRUD = shadowCRUDEnabled
 | 
			
		||||
      ? this.buildShadowCRUD()
 | 
			
		||||
    ? buildShadowCRUD()
 | 
			
		||||
    : createDefaultSchema();
 | 
			
		||||
 | 
			
		||||
  const _schema = strapi.plugins.graphql.config._schema.graphql;
 | 
			
		||||
@ -156,21 +154,21 @@ const schemaBuilder = {
 | 
			
		||||
 | 
			
		||||
  _schema.resolver = resolvers;
 | 
			
		||||
 | 
			
		||||
    this.buildResolvers(resolvers);
 | 
			
		||||
  buildResolvers(resolvers);
 | 
			
		||||
 | 
			
		||||
  // Return empty schema when there is no model.
 | 
			
		||||
  if (_.isEmpty(shadowCRUD.definition) && _.isEmpty(definition)) {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const queryFields = this.formatGQL(
 | 
			
		||||
  const queryFields = formatGQL(
 | 
			
		||||
    shadowCRUD.query,
 | 
			
		||||
    resolver.Query,
 | 
			
		||||
    null,
 | 
			
		||||
    'query'
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const mutationFields = this.formatGQL(
 | 
			
		||||
  const mutationFields = formatGQL(
 | 
			
		||||
    shadowCRUD.mutation,
 | 
			
		||||
    resolver.Mutation,
 | 
			
		||||
    null,
 | 
			
		||||
@ -206,7 +204,7 @@ const schemaBuilder = {
 | 
			
		||||
      resolvers,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
      this.writeGenerateSchema(graphql.printSchema(schema));
 | 
			
		||||
    writeGenerateSchema(graphql.printSchema(schema));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Remove custom scalar (like Upload);
 | 
			
		||||
@ -216,19 +214,19 @@ const schemaBuilder = {
 | 
			
		||||
    typeDefs: gql(typeDefs),
 | 
			
		||||
    resolvers,
 | 
			
		||||
  };
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
/**
 | 
			
		||||
 * Save into a file the readable GraphQL schema.
 | 
			
		||||
 *
 | 
			
		||||
 * @return void
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
  writeGenerateSchema: schema => {
 | 
			
		||||
const writeGenerateSchema = schema => {
 | 
			
		||||
  return strapi.fs.writeAppFile('exports/graphql/schema.graphql', schema);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  buildShadowCRUD() {
 | 
			
		||||
const buildShadowCRUD = () => {
 | 
			
		||||
  const modelSchema = Resolvers.buildShadowCRUD(
 | 
			
		||||
    _.omitBy(strapi.models, model => model.internal === true)
 | 
			
		||||
  );
 | 
			
		||||
@ -246,9 +244,9 @@ const schemaBuilder = {
 | 
			
		||||
  mergeSchemas(schema, modelSchema, ...pluginSchemas, ...componentSchemas);
 | 
			
		||||
 | 
			
		||||
  return schema;
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  buildResolvers(resolvers) {
 | 
			
		||||
const buildResolvers = resolvers => {
 | 
			
		||||
  // Transform object to only contain function.
 | 
			
		||||
  Object.keys(resolvers).reduce((acc, type) => {
 | 
			
		||||
    if (graphql.isScalarType(acc[type])) {
 | 
			
		||||
@ -284,7 +282,6 @@ const schemaBuilder = {
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, acc);
 | 
			
		||||
  }, resolvers);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: implement
 | 
			
		||||
@ -501,19 +498,23 @@ const getActionDetails = resolver => {
 | 
			
		||||
 */
 | 
			
		||||
const isResolvablePath = path => _.isString(path) && !_.isEmpty(path);
 | 
			
		||||
 | 
			
		||||
const getPolicies = (config, { plugin, api } = {}) => {
 | 
			
		||||
const getPolicies = config => {
 | 
			
		||||
  const { resolver, policies = [], resolverOf } = config;
 | 
			
		||||
 | 
			
		||||
  const { api, plugin } = config['_metadatas'] || {};
 | 
			
		||||
 | 
			
		||||
  const policyFns = [];
 | 
			
		||||
 | 
			
		||||
  const { controller, action } = isResolvablePath(resolverOf)
 | 
			
		||||
  const { controller, action, plugin: pathPlugin } = isResolvablePath(
 | 
			
		||||
    resolverOf
 | 
			
		||||
  )
 | 
			
		||||
    ? getActionDetails(resolverOf)
 | 
			
		||||
    : getActionDetails(resolver);
 | 
			
		||||
 | 
			
		||||
  const globalPolicy = policyUtils.globalPolicy({
 | 
			
		||||
    controller,
 | 
			
		||||
    action,
 | 
			
		||||
    plugin,
 | 
			
		||||
    plugin: pathPlugin,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  policyFns.push(globalPolicy);
 | 
			
		||||
@ -530,4 +531,9 @@ const getPolicies = (config, { plugin, api } = {}) => {
 | 
			
		||||
  return policyFns;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = schemaBuilder;
 | 
			
		||||
module.exports = {
 | 
			
		||||
  generateSchema,
 | 
			
		||||
  getDescription,
 | 
			
		||||
  formatGQL,
 | 
			
		||||
  buildQuery,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user