mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 11:54:10 +00:00 
			
		
		
		
	Use objects instead of strings to declare queries & mutattions
This commit is contained in:
		
							parent
							
								
									a408dc1940
								
							
						
					
					
						commit
						02342ace0a
					
				@ -1,6 +1,6 @@
 | 
			
		||||
module.exports = ({ env }) => ({
 | 
			
		||||
  graphql: {
 | 
			
		||||
    amountLimit: 5,
 | 
			
		||||
    amountLimit: 50,
 | 
			
		||||
    depthLimit: 10,
 | 
			
		||||
    apolloServer: {
 | 
			
		||||
      tracing: true,
 | 
			
		||||
 | 
			
		||||
@ -50,9 +50,7 @@ module.exports = strapi => {
 | 
			
		||||
      });
 | 
			
		||||
      _.merge(strapi, { api, plugins });
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
       * Create a merge of all the GraphQL configuration.
 | 
			
		||||
       */
 | 
			
		||||
      // 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 });
 | 
			
		||||
@ -68,10 +66,10 @@ module.exports = strapi => {
 | 
			
		||||
        return attachMetadataToResolvers(schema, { plugin: key });
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const baseSchema = mergeSchemas([...apisSchemas, ...pluginsSchemas, ...extensionsSchemas]);
 | 
			
		||||
      const baseSchema = mergeSchemas([...pluginsSchemas, ...extensionsSchemas, ...apisSchemas]);
 | 
			
		||||
 | 
			
		||||
      // save the final schema in the plugin's config
 | 
			
		||||
      _.set(strapi, ['plugins', 'graphql', 'config', '_schema', 'graphql'], baseSchema);
 | 
			
		||||
      _.set(strapi.plugins.graphql, 'config._schema.graphql', baseSchema);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initialize() {
 | 
			
		||||
 | 
			
		||||
@ -494,8 +494,9 @@ const formatConnectionAggregator = function(fields, model, modelName) {
 | 
			
		||||
 *  }
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
const formatModelConnectionsGQL = function({ fields, model, name, resolver }) {
 | 
			
		||||
  const { globalId } = model;
 | 
			
		||||
const formatModelConnectionsGQL = function({ fields, model: contentType, name, resolver }) {
 | 
			
		||||
  const { globalId } = contentType;
 | 
			
		||||
  const model = strapi.getModel(contentType.uid);
 | 
			
		||||
 | 
			
		||||
  const connectionGlobalId = `${globalId}Connection`;
 | 
			
		||||
 | 
			
		||||
@ -514,8 +515,6 @@ const formatModelConnectionsGQL = function({ fields, model, name, resolver }) {
 | 
			
		||||
  }
 | 
			
		||||
  modelConnectionTypes += groupByFormat.type;
 | 
			
		||||
 | 
			
		||||
  const queryName = `${pluralName}Connection(sort: String, limit: Int, start: Int, where: JSON)`;
 | 
			
		||||
 | 
			
		||||
  const connectionResolver = buildQueryResolver(`${pluralName}Connection.values`, resolver);
 | 
			
		||||
 | 
			
		||||
  const connectionQueryName = `${pluralName}Connection`;
 | 
			
		||||
@ -524,7 +523,16 @@ const formatModelConnectionsGQL = function({ fields, model, name, resolver }) {
 | 
			
		||||
    globalId: connectionGlobalId,
 | 
			
		||||
    definition: modelConnectionTypes,
 | 
			
		||||
    query: {
 | 
			
		||||
      [queryName]: connectionGlobalId,
 | 
			
		||||
      [`${pluralName}Connection`]: {
 | 
			
		||||
        args: {
 | 
			
		||||
          sort: 'String',
 | 
			
		||||
          limit: 'Int',
 | 
			
		||||
          start: 'Int',
 | 
			
		||||
          where: 'JSON',
 | 
			
		||||
          ...(resolver.args || {}),
 | 
			
		||||
        },
 | 
			
		||||
        type: connectionGlobalId,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    resolvers: {
 | 
			
		||||
      Query: {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/strapi-plugin-graphql/services/hooks.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/strapi-plugin-graphql/services/hooks.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { createAsyncSeriesWaterfallHook } = require('strapi-utils').hooks;
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  createQuery: createAsyncSeriesWaterfallHook(),
 | 
			
		||||
  createMutation: createAsyncSeriesWaterfallHook(),
 | 
			
		||||
};
 | 
			
		||||
@ -75,6 +75,11 @@ const buildMutationContext = ({ options, graphqlContext }) => {
 | 
			
		||||
    ctx.request.body = options;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // pass extra args as ctx.query
 | 
			
		||||
  if (options.input) {
 | 
			
		||||
    ctx.query = convertToParams(_.omit(options, 'input'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return ctx;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -71,14 +71,38 @@ const fieldsToSDL = ({ fields, configurations, model }) => {
 | 
			
		||||
const operationToSDL = ({ fields, configurations }) => {
 | 
			
		||||
  return Object.entries(fields)
 | 
			
		||||
    .map(([key, value]) => {
 | 
			
		||||
      const [attr] = key.split('(');
 | 
			
		||||
      const attributeName = _.trim(attr);
 | 
			
		||||
      if (typeof value === 'string') {
 | 
			
		||||
        const [attr] = key.split('(');
 | 
			
		||||
        const attributeName = _.trim(attr);
 | 
			
		||||
 | 
			
		||||
      return applyMetadatas(`${key}: ${value}`, configurations[attributeName]);
 | 
			
		||||
        return applyMetadatas(`${key}: ${value}`, configurations[attributeName]);
 | 
			
		||||
      } else {
 | 
			
		||||
        const { args = {}, type } = value;
 | 
			
		||||
 | 
			
		||||
        const query = `${key}${argumentsToSDL(args)}: ${type}`;
 | 
			
		||||
        return applyMetadatas(query, configurations[key]);
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    .join('\n');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Converts an object of arguments into graphql SDL
 | 
			
		||||
 * @param {object} args arguments
 | 
			
		||||
 * @returns {string}
 | 
			
		||||
 */
 | 
			
		||||
const argumentsToSDL = args => {
 | 
			
		||||
  if (_.isEmpty(args)) {
 | 
			
		||||
    return '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const sdlArgs = Object.entries(args)
 | 
			
		||||
    .map(([key, value]) => `${key}: ${value}`)
 | 
			
		||||
    .join(', ');
 | 
			
		||||
 | 
			
		||||
  return `(${sdlArgs})`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Applies description and deprecated to a field definition
 | 
			
		||||
 * @param {string} definition field definition
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,8 @@ const _ = require('lodash');
 | 
			
		||||
const graphql = require('graphql');
 | 
			
		||||
const PublicationState = require('../types/publication-state');
 | 
			
		||||
const Types = require('./type-builder');
 | 
			
		||||
const { buildModels } = require('./type-definitions');
 | 
			
		||||
const { mergeSchemas, createDefaultSchema, diffResolvers } = require('./utils');
 | 
			
		||||
const buildShadowCrud = require('./shadow-crud');
 | 
			
		||||
const { createDefaultSchema, diffResolvers } = require('./utils');
 | 
			
		||||
const { toSDL } = require('./schema-definitions');
 | 
			
		||||
const { buildQuery, buildMutation } = require('./resolvers-builder');
 | 
			
		||||
 | 
			
		||||
@ -27,11 +27,15 @@ const generateSchema = () => {
 | 
			
		||||
  const isFederated = _.get(strapi.plugins.graphql.config, 'federation', false);
 | 
			
		||||
  const shadowCRUDEnabled = strapi.plugins.graphql.config.shadowCRUD !== false;
 | 
			
		||||
 | 
			
		||||
  // Generate type definition and query/mutation for models.
 | 
			
		||||
  const shadowCRUD = shadowCRUDEnabled ? buildModelsShadowCRUD() : createDefaultSchema();
 | 
			
		||||
 | 
			
		||||
  const _schema = strapi.plugins.graphql.config._schema.graphql;
 | 
			
		||||
 | 
			
		||||
  const ctx = {
 | 
			
		||||
    schema: _schema,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Generate type definition and query/mutation for models.
 | 
			
		||||
  const shadowCRUD = shadowCRUDEnabled ? buildShadowCrud(ctx) : createDefaultSchema();
 | 
			
		||||
 | 
			
		||||
  // Extract custom definition, query or resolver.
 | 
			
		||||
  const { definition, query, mutation, resolver = {} } = _schema;
 | 
			
		||||
 | 
			
		||||
@ -73,17 +77,19 @@ const generateSchema = () => {
 | 
			
		||||
      ${Types.addInput()}
 | 
			
		||||
 | 
			
		||||
      ${PublicationState.definition}
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      type AdminUser {
 | 
			
		||||
        id: ID!
 | 
			
		||||
        username: String
 | 
			
		||||
        firstname: String!
 | 
			
		||||
        lastname: String!
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      type Query {
 | 
			
		||||
        ${queryFields}
 | 
			
		||||
        ${query}
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      type Mutation {
 | 
			
		||||
        ${mutationFields}
 | 
			
		||||
        ${mutation}
 | 
			
		||||
@ -133,14 +139,6 @@ const writeGenerateSchema = schema => {
 | 
			
		||||
  return strapi.fs.writeAppFile('exports/graphql/schema.graphql', printSchema);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const buildModelsShadowCRUD = () => {
 | 
			
		||||
  const models = Object.values(strapi.contentTypes).filter(model => model.plugin !== 'admin');
 | 
			
		||||
 | 
			
		||||
  const components = Object.values(strapi.components);
 | 
			
		||||
 | 
			
		||||
  return mergeSchemas(createDefaultSchema(), ...buildModels([...models, ...components]));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const buildResolvers = resolvers => {
 | 
			
		||||
  // Transform object to only contain function.
 | 
			
		||||
  return Object.keys(resolvers).reduce((acc, type) => {
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,5 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * GraphQL.js service
 | 
			
		||||
 *
 | 
			
		||||
 * @description: A set of functions similar to controller's actions to avoid code duplication.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const _ = require('lodash');
 | 
			
		||||
const { contentTypes } = require('strapi-utils');
 | 
			
		||||
 | 
			
		||||
@ -18,16 +12,46 @@ const DynamicZoneScalar = require('../types/dynamiczoneScalar');
 | 
			
		||||
 | 
			
		||||
const { formatModelConnectionsGQL } = require('./build-aggregation');
 | 
			
		||||
const types = require('./type-builder');
 | 
			
		||||
const { mergeSchemas, convertToParams, convertToQuery, amountLimiting } = require('./utils');
 | 
			
		||||
const {
 | 
			
		||||
  actionExists,
 | 
			
		||||
  mergeSchemas,
 | 
			
		||||
  convertToParams,
 | 
			
		||||
  convertToQuery,
 | 
			
		||||
  amountLimiting,
 | 
			
		||||
  createDefaultSchema,
 | 
			
		||||
} = require('./utils');
 | 
			
		||||
const { toSDL, getTypeDescription } = require('./schema-definitions');
 | 
			
		||||
const { toSingular, toPlural } = require('./naming');
 | 
			
		||||
const { buildQuery, buildMutation } = require('./resolvers-builder');
 | 
			
		||||
const { actionExists } = require('./utils');
 | 
			
		||||
 | 
			
		||||
const OPTIONS = Symbol();
 | 
			
		||||
 | 
			
		||||
const FIND_QUERY_ARGUMENTS = `(sort: String, limit: Int, start: Int, where: JSON, publicationState: PublicationState)`;
 | 
			
		||||
const FIND_ONE_QUERY_ARGUMENTS = `(id: ID!, publicationState: PublicationState)`;
 | 
			
		||||
const FIND_QUERY_ARGUMENTS = {
 | 
			
		||||
  sort: 'String',
 | 
			
		||||
  limit: 'Int',
 | 
			
		||||
  start: 'Int',
 | 
			
		||||
  where: 'JSON',
 | 
			
		||||
  publicationState: 'PublicationState',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const FIND_ONE_QUERY_ARGUMENTS = {
 | 
			
		||||
  id: 'ID!',
 | 
			
		||||
  publicationState: 'PublicationState',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Builds a graphql schema from all the contentTypes & components loaded
 | 
			
		||||
 * @param {{ schema: object }} ctx
 | 
			
		||||
 * @returns {object}
 | 
			
		||||
 */
 | 
			
		||||
const buildShadowCrud = ctx => {
 | 
			
		||||
  const models = Object.values(strapi.contentTypes).filter(model => model.plugin !== 'admin');
 | 
			
		||||
  const components = Object.values(strapi.components);
 | 
			
		||||
 | 
			
		||||
  const allSchemas = buildModels([...models, ...components], ctx);
 | 
			
		||||
 | 
			
		||||
  return mergeSchemas(createDefaultSchema(), ...allSchemas);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const assignOptions = (element, parent) => {
 | 
			
		||||
  if (Array.isArray(element)) {
 | 
			
		||||
@ -41,10 +65,18 @@ const isQueryEnabled = (schema, name) => {
 | 
			
		||||
  return _.get(schema, `resolver.Query.${name}`) !== false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getQueryInfo = (schema, name) => {
 | 
			
		||||
  return _.get(schema, `resolver.Query.${name}`, {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isMutationEnabled = (schema, name) => {
 | 
			
		||||
  return _.get(schema, `resolver.Mutation.${name}`) !== false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getMutationInfo = (schema, name) => {
 | 
			
		||||
  return _.get(schema, `resolver.Mutation.${name}`, {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isNotPrivate = _.curry((model, attributeName) => {
 | 
			
		||||
  return !contentTypes.isPrivateAttribute(model, attributeName);
 | 
			
		||||
});
 | 
			
		||||
@ -294,19 +326,19 @@ const buildAssocResolvers = model => {
 | 
			
		||||
 *
 | 
			
		||||
 * @return Object
 | 
			
		||||
 */
 | 
			
		||||
const buildModels = models => {
 | 
			
		||||
const buildModels = (models, ctx) => {
 | 
			
		||||
  return models.map(model => {
 | 
			
		||||
    const { kind, modelType } = model;
 | 
			
		||||
 | 
			
		||||
    if (modelType === 'component') {
 | 
			
		||||
      return buildComponent(model);
 | 
			
		||||
      return buildComponent(model, ctx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (kind) {
 | 
			
		||||
      case 'singleType':
 | 
			
		||||
        return buildSingleType(model);
 | 
			
		||||
        return buildSingleType(model, ctx);
 | 
			
		||||
      default:
 | 
			
		||||
        return buildCollectionType(model);
 | 
			
		||||
        return buildCollectionType(model, ctx);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@ -353,14 +385,12 @@ const buildComponent = component => {
 | 
			
		||||
  return schema;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const buildSingleType = model => {
 | 
			
		||||
const buildSingleType = (model, ctx) => {
 | 
			
		||||
  const { uid, modelName } = model;
 | 
			
		||||
 | 
			
		||||
  const singularName = toSingular(modelName);
 | 
			
		||||
 | 
			
		||||
  const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
 | 
			
		||||
 | 
			
		||||
  const globalType = _.get(_schema, `type.${model.globalId}`, {});
 | 
			
		||||
  const globalType = _.get(ctx.schema, `type.${model.globalId}`, {});
 | 
			
		||||
 | 
			
		||||
  const localSchema = buildModelDefinition(model, globalType);
 | 
			
		||||
 | 
			
		||||
@ -369,22 +399,32 @@ const buildSingleType = model => {
 | 
			
		||||
    return localSchema;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isQueryEnabled(_schema, singularName)) {
 | 
			
		||||
    const resolver = buildQuery(singularName, {
 | 
			
		||||
  if (isQueryEnabled(ctx.schema, singularName)) {
 | 
			
		||||
    const resolverOpts = {
 | 
			
		||||
      resolver: `${uid}.find`,
 | 
			
		||||
      ..._.get(_schema, `resolver.Query.${singularName}`, {}),
 | 
			
		||||
    });
 | 
			
		||||
      ...getQueryInfo(ctx.schema, singularName),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _.merge(localSchema, {
 | 
			
		||||
    const resolver = buildQuery(singularName, resolverOpts);
 | 
			
		||||
 | 
			
		||||
    const query = {
 | 
			
		||||
      query: {
 | 
			
		||||
        [`${singularName}(publicationState: PublicationState)`]: model.globalId,
 | 
			
		||||
        [singularName]: {
 | 
			
		||||
          args: {
 | 
			
		||||
            publicationState: 'PublicationState',
 | 
			
		||||
            ...(resolverOpts.args || {}),
 | 
			
		||||
          },
 | 
			
		||||
          type: model.globalId,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      resolvers: {
 | 
			
		||||
        Query: {
 | 
			
		||||
          [singularName]: wrapPublicationStateResolver(resolver),
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _.merge(localSchema, query);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add model Input definition.
 | 
			
		||||
@ -392,7 +432,7 @@ const buildSingleType = model => {
 | 
			
		||||
 | 
			
		||||
  // build every mutation
 | 
			
		||||
  ['update', 'delete'].forEach(action => {
 | 
			
		||||
    const mutationSchema = buildMutationTypeDef({ model, action }, { _schema });
 | 
			
		||||
    const mutationSchema = buildMutationTypeDef({ model, action }, ctx);
 | 
			
		||||
 | 
			
		||||
    mergeSchemas(localSchema, mutationSchema);
 | 
			
		||||
  });
 | 
			
		||||
@ -400,15 +440,13 @@ const buildSingleType = model => {
 | 
			
		||||
  return localSchema;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const buildCollectionType = model => {
 | 
			
		||||
const buildCollectionType = (model, ctx) => {
 | 
			
		||||
  const { plugin, modelName, uid } = model;
 | 
			
		||||
 | 
			
		||||
  const singularName = toSingular(modelName);
 | 
			
		||||
  const pluralName = toPlural(modelName);
 | 
			
		||||
 | 
			
		||||
  const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
 | 
			
		||||
 | 
			
		||||
  const globalType = _.get(_schema, `type.${model.globalId}`, {});
 | 
			
		||||
  const globalType = _.get(ctx.schema, `type.${model.globalId}`, {});
 | 
			
		||||
 | 
			
		||||
  const localSchema = buildModelDefinition(model, globalType);
 | 
			
		||||
  const { typeDefObj } = localSchema;
 | 
			
		||||
@ -418,46 +456,65 @@ const buildCollectionType = model => {
 | 
			
		||||
    return localSchema;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isQueryEnabled(_schema, singularName)) {
 | 
			
		||||
  if (isQueryEnabled(ctx.schema, singularName)) {
 | 
			
		||||
    const resolverOpts = {
 | 
			
		||||
      resolver: `${uid}.findOne`,
 | 
			
		||||
      ..._.get(_schema, `resolver.Query.${singularName}`, {}),
 | 
			
		||||
      ...getQueryInfo(ctx.schema, singularName),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (actionExists(resolverOpts)) {
 | 
			
		||||
      const resolver = buildQuery(singularName, resolverOpts);
 | 
			
		||||
      _.merge(localSchema, {
 | 
			
		||||
 | 
			
		||||
      const query = {
 | 
			
		||||
        query: {
 | 
			
		||||
          // TODO: support all the unique fields
 | 
			
		||||
          [`${singularName}${FIND_ONE_QUERY_ARGUMENTS}`]: model.globalId,
 | 
			
		||||
          [singularName]: {
 | 
			
		||||
            args: {
 | 
			
		||||
              ...FIND_ONE_QUERY_ARGUMENTS,
 | 
			
		||||
              ...(resolverOpts.args || {}),
 | 
			
		||||
            },
 | 
			
		||||
            type: model.globalId,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        resolvers: {
 | 
			
		||||
          Query: {
 | 
			
		||||
            [singularName]: wrapPublicationStateResolver(resolver),
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      _.merge(localSchema, query);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isQueryEnabled(_schema, pluralName)) {
 | 
			
		||||
  if (isQueryEnabled(ctx.schema, pluralName)) {
 | 
			
		||||
    const resolverOpts = {
 | 
			
		||||
      resolver: `${uid}.find`,
 | 
			
		||||
      ..._.get(_schema, `resolver.Query.${pluralName}`, {}),
 | 
			
		||||
      ...getQueryInfo(ctx.schema, pluralName),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (actionExists(resolverOpts)) {
 | 
			
		||||
      const resolver = buildQuery(pluralName, resolverOpts);
 | 
			
		||||
      _.merge(localSchema, {
 | 
			
		||||
 | 
			
		||||
      const query = {
 | 
			
		||||
        query: {
 | 
			
		||||
          [`${pluralName}${FIND_QUERY_ARGUMENTS}`]: `[${model.globalId}]`,
 | 
			
		||||
          [pluralName]: {
 | 
			
		||||
            args: {
 | 
			
		||||
              ...FIND_QUERY_ARGUMENTS,
 | 
			
		||||
              ...(resolverOpts.args || {}),
 | 
			
		||||
            },
 | 
			
		||||
            type: `[${model.globalId}]`,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        resolvers: {
 | 
			
		||||
          Query: {
 | 
			
		||||
            [pluralName]: wrapPublicationStateResolver(resolver),
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (isQueryEnabled(_schema, `${pluralName}Connection`)) {
 | 
			
		||||
      _.merge(localSchema, query);
 | 
			
		||||
 | 
			
		||||
      if (isQueryEnabled(ctx.schema, `${pluralName}Connection`)) {
 | 
			
		||||
        // Generate the aggregation for the given model
 | 
			
		||||
        const aggregationSchema = formatModelConnectionsGQL({
 | 
			
		||||
          fields: typeDefObj,
 | 
			
		||||
@ -477,7 +534,7 @@ const buildCollectionType = model => {
 | 
			
		||||
 | 
			
		||||
  // build every mutation
 | 
			
		||||
  ['create', 'update', 'delete'].forEach(action => {
 | 
			
		||||
    const mutationSchema = buildMutationTypeDef({ model, action }, { _schema });
 | 
			
		||||
    const mutationSchema = buildMutationTypeDef({ model, action }, ctx);
 | 
			
		||||
    mergeSchemas(localSchema, mutationSchema);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -487,14 +544,14 @@ const buildCollectionType = model => {
 | 
			
		||||
// TODO:
 | 
			
		||||
// - Implement batch methods (need to update the content-manager as well).
 | 
			
		||||
// - Implement nested transactional methods (create/update).
 | 
			
		||||
const buildMutationTypeDef = ({ model, action }, { _schema }) => {
 | 
			
		||||
const buildMutationTypeDef = ({ model, action }, ctx) => {
 | 
			
		||||
  const capitalizedName = _.upperFirst(toSingular(model.modelName));
 | 
			
		||||
  const mutationName = `${action}${capitalizedName}`;
 | 
			
		||||
 | 
			
		||||
  const resolverOpts = {
 | 
			
		||||
    resolver: `${model.uid}.${action}`,
 | 
			
		||||
    transformOutput: result => ({ [toSingular(model.modelName)]: result }),
 | 
			
		||||
    ..._.get(_schema, `resolver.Mutation.${mutationName}`, {}),
 | 
			
		||||
    ...getMutationInfo(ctx.schema, mutationName),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (!actionExists(resolverOpts)) {
 | 
			
		||||
@ -509,7 +566,7 @@ const buildMutationTypeDef = ({ model, action }, { _schema }) => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // ignore if disabled
 | 
			
		||||
  if (!isMutationEnabled(_schema, mutationName)) {
 | 
			
		||||
  if (!isMutationEnabled(ctx.schema, mutationName)) {
 | 
			
		||||
    return {
 | 
			
		||||
      definition,
 | 
			
		||||
    };
 | 
			
		||||
@ -517,15 +574,24 @@ const buildMutationTypeDef = ({ model, action }, { _schema }) => {
 | 
			
		||||
 | 
			
		||||
  const { kind } = model;
 | 
			
		||||
 | 
			
		||||
  let mutationDef = `${mutationName}(input: ${mutationName}Input)`;
 | 
			
		||||
  if (kind === 'singleType' && action === 'delete') {
 | 
			
		||||
    mutationDef = mutationName;
 | 
			
		||||
  const args = {};
 | 
			
		||||
 | 
			
		||||
  if (kind !== 'singleType' || action !== 'delete') {
 | 
			
		||||
    Object.assign(args, {
 | 
			
		||||
      input: `${mutationName}Input`,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    definition,
 | 
			
		||||
    mutation: {
 | 
			
		||||
      [mutationDef]: `${mutationName}Payload`,
 | 
			
		||||
      [mutationName]: {
 | 
			
		||||
        args: {
 | 
			
		||||
          ...args,
 | 
			
		||||
          ...(resolverOpts.args || {}),
 | 
			
		||||
        },
 | 
			
		||||
        type: `${mutationName}Payload`,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    resolvers: {
 | 
			
		||||
      Mutation: {
 | 
			
		||||
@ -535,6 +601,4 @@ const buildMutationTypeDef = ({ model, action }, { _schema }) => {
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  buildModels,
 | 
			
		||||
};
 | 
			
		||||
module.exports = buildShadowCrud;
 | 
			
		||||
@ -4,24 +4,39 @@ const _ = require('lodash');
 | 
			
		||||
const { QUERY_OPERATORS } = require('strapi-utils');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Merges
 | 
			
		||||
 * @typedef {object} Schema
 | 
			
		||||
 * @property {object} resolvers
 | 
			
		||||
 * @property {object} mutation
 | 
			
		||||
 * @property {object} query
 | 
			
		||||
 * @property {string} definition
 | 
			
		||||
 */
 | 
			
		||||
const mergeSchemas = (root, ...subs) => {
 | 
			
		||||
  subs.forEach(sub => {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Merges strapi graphql schema together
 | 
			
		||||
 * @param {Schema} object - destination object
 | 
			
		||||
 * @param  {Schema[]} sources - source objects to merge into the destination object
 | 
			
		||||
 * @returns {Schema}
 | 
			
		||||
 */
 | 
			
		||||
const mergeSchemas = (object, ...sources) => {
 | 
			
		||||
  sources.forEach(sub => {
 | 
			
		||||
    if (_.isEmpty(sub)) return;
 | 
			
		||||
    const { definition = '', query = {}, mutation = {}, resolvers = {} } = sub;
 | 
			
		||||
 | 
			
		||||
    root.definition += '\n' + definition;
 | 
			
		||||
    _.merge(root, {
 | 
			
		||||
    object.definition += '\n' + definition;
 | 
			
		||||
    _.merge(object, {
 | 
			
		||||
      query,
 | 
			
		||||
      mutation,
 | 
			
		||||
      resolvers,
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return root;
 | 
			
		||||
  return object;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns an empty schema
 | 
			
		||||
 * @returns {Schema}
 | 
			
		||||
 */
 | 
			
		||||
const createDefaultSchema = () => ({
 | 
			
		||||
  definition: '',
 | 
			
		||||
  query: {},
 | 
			
		||||
 | 
			
		||||
@ -205,6 +205,20 @@ const addCreateLocalizationAction = contentType => {
 | 
			
		||||
  _.set(strapi, coreApiControllerPath, handler);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mergeCustomizer = (dest, src) => {
 | 
			
		||||
  if (typeof dest === 'string') {
 | 
			
		||||
    return `${dest}\n${src}`;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a graphql schema to the plugin's global graphl schema to be processed
 | 
			
		||||
 * @param {object} schema
 | 
			
		||||
 */
 | 
			
		||||
const addGraphqlSchema = schema => {
 | 
			
		||||
  _.mergeWith(strapi.plugins.i18n.config.schema.graphql, schema, mergeCustomizer);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add localization mutation & filters to use with the graphql plugin
 | 
			
		||||
 * @param {object} contentType
 | 
			
		||||
@ -216,27 +230,61 @@ const addGraphqlLocalizationAction = contentType => {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const typeName = _.capitalize(modelName);
 | 
			
		||||
  _.mergeWith(
 | 
			
		||||
    strapi.plugins.i18n.config.schema.graphql,
 | 
			
		||||
    {
 | 
			
		||||
      mutation: `
 | 
			
		||||
          create${typeName}Localization(input: update${typeName}Input!): ${typeName}!
 | 
			
		||||
        `,
 | 
			
		||||
  const { toSingular, toPlural } = strapi.plugins.graphql.services.naming;
 | 
			
		||||
 | 
			
		||||
  // We use a string instead of an enum as the locales can be changed in the admin
 | 
			
		||||
  // NOTE: We could use a custom scalar so the validation becomes dynamic
 | 
			
		||||
  const localeArgs = {
 | 
			
		||||
    args: {
 | 
			
		||||
      locale: 'String',
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // add locale arguments in the existing queries
 | 
			
		||||
  if (isSingleType(contentType)) {
 | 
			
		||||
    const queryName = toSingular(modelName);
 | 
			
		||||
    const mutationSuffix = _.upperFirst(queryName);
 | 
			
		||||
 | 
			
		||||
    addGraphqlSchema({
 | 
			
		||||
      resolver: {
 | 
			
		||||
        Query: {
 | 
			
		||||
          [queryName]: localeArgs,
 | 
			
		||||
        },
 | 
			
		||||
        Mutation: {
 | 
			
		||||
          [`create${typeName}Localization`]: {
 | 
			
		||||
            resolver: `application::${modelName}.${modelName}.createLocalization`,
 | 
			
		||||
          },
 | 
			
		||||
          [`update${mutationSuffix}`]: localeArgs,
 | 
			
		||||
          [`delete${mutationSuffix}`]: localeArgs,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    const queryName = toPlural(modelName);
 | 
			
		||||
 | 
			
		||||
    addGraphqlSchema({
 | 
			
		||||
      resolver: {
 | 
			
		||||
        Query: {
 | 
			
		||||
          [queryName]: localeArgs,
 | 
			
		||||
          [`${queryName}Connection`]: localeArgs,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // add new mutation to create a localization
 | 
			
		||||
  const typeName = _.capitalize(modelName);
 | 
			
		||||
  const mutationName = `create${typeName}Localization`;
 | 
			
		||||
  const mutationDef = `${mutationName}(input: update${typeName}Input!): ${typeName}!`;
 | 
			
		||||
  const actionName = `${contentType.uid}.createLocalization`;
 | 
			
		||||
 | 
			
		||||
  addGraphqlSchema({
 | 
			
		||||
    mutation: mutationDef,
 | 
			
		||||
    resolver: {
 | 
			
		||||
      Mutation: {
 | 
			
		||||
        [mutationName]: {
 | 
			
		||||
          resolver: actionName,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    (dest, src) => {
 | 
			
		||||
      if (typeof dest === 'string') {
 | 
			
		||||
        return `${dest}\n${src}`;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user