mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			186 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
/**
 | 
						|
 * GraphQL.js service
 | 
						|
 *
 | 
						|
 * @description: A set of functions similar to controller's actions to avoid code duplication.
 | 
						|
 */
 | 
						|
const { filterSchema } = require('@graphql-tools/utils');
 | 
						|
const { buildFederatedSchema } = require('@apollo/federation');
 | 
						|
const { gql, makeExecutableSchema } = require('apollo-server-koa');
 | 
						|
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 { toSDL } = require('./schema-definitions');
 | 
						|
const { buildQuery, buildMutation } = require('./resolvers-builder');
 | 
						|
 | 
						|
/**
 | 
						|
 * Generate GraphQL schema.
 | 
						|
 *
 | 
						|
 * @return Schema
 | 
						|
 */
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
  // Extract custom definition, query or resolver.
 | 
						|
  const { definition, query, mutation, resolver = {} } = _schema;
 | 
						|
 | 
						|
  // Polymorphic.
 | 
						|
  const polymorphicSchema = Types.addPolymorphicUnionType(definition + shadowCRUD.definition);
 | 
						|
 | 
						|
  const builtResolvers = _.merge({}, shadowCRUD.resolvers, polymorphicSchema.resolvers);
 | 
						|
 | 
						|
  const extraResolvers = diffResolvers(_schema.resolver, builtResolvers);
 | 
						|
 | 
						|
  const resolvers = _.merge({}, builtResolvers, buildResolvers(extraResolvers));
 | 
						|
 | 
						|
  // Return empty schema when there is no model.
 | 
						|
  if (_.isEmpty(shadowCRUD.definition) && _.isEmpty(definition)) {
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
 | 
						|
  const queryFields = shadowCRUD.query && toSDL(shadowCRUD.query, resolver.Query, null, 'query');
 | 
						|
 | 
						|
  const mutationFields =
 | 
						|
    shadowCRUD.mutation && toSDL(shadowCRUD.mutation, resolver.Mutation, null, 'mutation');
 | 
						|
 | 
						|
  Object.assign(resolvers, PublicationState.resolver);
 | 
						|
 | 
						|
  const scalars = Types.getScalars();
 | 
						|
 | 
						|
  Object.assign(resolvers, scalars);
 | 
						|
 | 
						|
  const scalarDef = Object.keys(scalars)
 | 
						|
    .map(key => `scalar ${key}`)
 | 
						|
    .join('\n');
 | 
						|
 | 
						|
  // Concatenate.
 | 
						|
  let typeDefs = `
 | 
						|
      ${definition}
 | 
						|
      ${shadowCRUD.definition}
 | 
						|
      ${polymorphicSchema.definition}
 | 
						|
      ${Types.addInput()}
 | 
						|
      
 | 
						|
      ${PublicationState.definition}
 | 
						|
      type AdminUser {
 | 
						|
        id: ID!
 | 
						|
        username: String
 | 
						|
        firstname: String!
 | 
						|
        lastname: String!
 | 
						|
      }
 | 
						|
      type Query {
 | 
						|
        ${queryFields}
 | 
						|
        ${query}
 | 
						|
      }
 | 
						|
      type Mutation {
 | 
						|
        ${mutationFields}
 | 
						|
        ${mutation}
 | 
						|
      }
 | 
						|
      ${scalarDef}
 | 
						|
    `;
 | 
						|
 | 
						|
  // Build schema.
 | 
						|
  const schema = makeExecutableSchema({
 | 
						|
    typeDefs,
 | 
						|
    resolvers,
 | 
						|
  });
 | 
						|
 | 
						|
  const generatedSchema = filterDisabledResolvers(schema, extraResolvers);
 | 
						|
 | 
						|
  if (strapi.config.environment !== 'production') {
 | 
						|
    writeGenerateSchema(generatedSchema);
 | 
						|
  }
 | 
						|
 | 
						|
  return isFederated ? getFederatedSchema(generatedSchema, resolvers) : generatedSchema;
 | 
						|
};
 | 
						|
 | 
						|
const getFederatedSchema = (schema, resolvers) =>
 | 
						|
  buildFederatedSchema([{ typeDefs: gql(graphql.printSchema(schema)), resolvers }]);
 | 
						|
 | 
						|
const filterDisabledResolvers = (schema, extraResolvers) =>
 | 
						|
  filterSchema({
 | 
						|
    schema,
 | 
						|
    rootFieldFilter: (operationName, fieldName) => {
 | 
						|
      const resolver = _.get(extraResolvers[operationName], fieldName, true);
 | 
						|
 | 
						|
      // resolvers set to false are filtered from the schema
 | 
						|
      if (resolver === false) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
      return true;
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
/**
 | 
						|
 * Save into a file the readable GraphQL schema.
 | 
						|
 *
 | 
						|
 * @return void
 | 
						|
 */
 | 
						|
const writeGenerateSchema = schema => {
 | 
						|
  const printSchema = graphql.printSchema(schema);
 | 
						|
  return strapi.fs.writeAppFile('exports/graphql/schema.graphql', printSchema);
 | 
						|
};
 | 
						|
 | 
						|
const buildModelsShadowCRUD = () => {
 | 
						|
  const models = Object.values(strapi.models).filter(model => model.internal !== true);
 | 
						|
 | 
						|
  const pluginModels = Object.values(strapi.plugins)
 | 
						|
    .map(plugin => Object.values(plugin.models) || [])
 | 
						|
    .reduce((acc, arr) => acc.concat(arr), []);
 | 
						|
 | 
						|
  const components = Object.values(strapi.components);
 | 
						|
 | 
						|
  return mergeSchemas(
 | 
						|
    createDefaultSchema(),
 | 
						|
    ...buildModels([...models, ...pluginModels, ...components])
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
const buildResolvers = resolvers => {
 | 
						|
  // Transform object to only contain function.
 | 
						|
  return Object.keys(resolvers).reduce((acc, type) => {
 | 
						|
    if (graphql.isScalarType(resolvers[type])) {
 | 
						|
      return acc;
 | 
						|
    }
 | 
						|
 | 
						|
    return Object.keys(resolvers[type]).reduce((acc, resolverName) => {
 | 
						|
      const resolverObj = resolvers[type][resolverName];
 | 
						|
 | 
						|
      // Disabled this query.
 | 
						|
      if (resolverObj === false) return acc;
 | 
						|
 | 
						|
      if (_.isFunction(resolverObj)) {
 | 
						|
        return _.set(acc, [type, resolverName], resolverObj);
 | 
						|
      }
 | 
						|
 | 
						|
      switch (type) {
 | 
						|
        case 'Mutation': {
 | 
						|
          _.set(acc, [type, resolverName], buildMutation(resolverName, resolverObj));
 | 
						|
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        default: {
 | 
						|
          _.set(acc, [type, resolverName], buildQuery(resolverName, resolverObj));
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      return acc;
 | 
						|
    }, acc);
 | 
						|
  }, {});
 | 
						|
};
 | 
						|
 | 
						|
module.exports = {
 | 
						|
  generateSchema,
 | 
						|
};
 |