2020-02-10 19:13:01 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* GraphQL.js service
|
|
|
|
*
|
|
|
|
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const { gql, makeExecutableSchema } = require('apollo-server-koa');
|
|
|
|
const _ = require('lodash');
|
|
|
|
const graphql = require('graphql');
|
|
|
|
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 shadowCRUDEnabled = strapi.plugins.graphql.config.shadowCRUD !== false;
|
|
|
|
|
|
|
|
// Generate type definition and query/mutation for models.
|
2020-03-02 15:18:08 +01:00
|
|
|
const shadowCRUD = shadowCRUDEnabled ? buildModelsShadowCRUD() : createDefaultSchema();
|
2020-02-10 19:13:01 +01:00
|
|
|
|
|
|
|
const _schema = strapi.plugins.graphql.config._schema.graphql;
|
|
|
|
|
|
|
|
// Extract custom definition, query or resolver.
|
|
|
|
const { definition, query, mutation, resolver = {} } = _schema;
|
|
|
|
|
|
|
|
// Polymorphic.
|
2020-03-02 15:18:08 +01:00
|
|
|
const polymorphicSchema = Types.addPolymorphicUnionType(definition + shadowCRUD.definition);
|
2020-02-10 19:13:01 +01:00
|
|
|
|
2020-03-02 15:18:08 +01:00
|
|
|
const builtResolvers = _.merge({}, shadowCRUD.resolvers, polymorphicSchema.resolvers);
|
2020-02-10 19:13:01 +01:00
|
|
|
|
|
|
|
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 {};
|
|
|
|
}
|
|
|
|
|
2020-03-02 15:18:08 +01:00
|
|
|
const queryFields = shadowCRUD.query && toSDL(shadowCRUD.query, resolver.Query, null, 'query');
|
2020-02-10 19:13:01 +01:00
|
|
|
|
|
|
|
const mutationFields =
|
2020-03-02 15:18:08 +01:00
|
|
|
shadowCRUD.mutation && toSDL(shadowCRUD.mutation, resolver.Mutation, null, 'mutation');
|
2020-02-10 19:13:01 +01:00
|
|
|
|
|
|
|
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()}
|
|
|
|
|
|
|
|
type Query {
|
|
|
|
${queryFields}
|
|
|
|
${query}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Mutation {
|
|
|
|
${mutationFields}
|
|
|
|
${mutation}
|
|
|
|
}
|
|
|
|
|
|
|
|
${scalarDef}
|
|
|
|
`;
|
|
|
|
|
|
|
|
// // Build schema.
|
2020-04-06 18:51:58 +02:00
|
|
|
if (strapi.config.environment !== 'production') {
|
2020-02-10 19:13:01 +01:00
|
|
|
// Write schema.
|
|
|
|
const schema = makeExecutableSchema({
|
|
|
|
typeDefs,
|
|
|
|
resolvers,
|
|
|
|
});
|
|
|
|
|
|
|
|
writeGenerateSchema(graphql.printSchema(schema));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove custom scalar (like Upload);
|
|
|
|
typeDefs = Types.removeCustomScalar(typeDefs, resolvers);
|
|
|
|
|
|
|
|
return {
|
|
|
|
typeDefs: gql(typeDefs),
|
|
|
|
resolvers,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save into a file the readable GraphQL schema.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
|
|
|
|
const writeGenerateSchema = schema => {
|
|
|
|
return strapi.fs.writeAppFile('exports/graphql/schema.graphql', schema);
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildModelsShadowCRUD = () => {
|
2020-03-02 15:18:08 +01:00
|
|
|
const models = Object.values(strapi.models).filter(model => model.internal !== true);
|
2020-02-10 19:13:01 +01:00
|
|
|
|
|
|
|
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': {
|
2020-03-02 15:18:08 +01:00
|
|
|
_.set(acc, [type, resolverName], buildMutation(resolverName, resolverObj));
|
2020-02-10 19:13:01 +01:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
2020-03-02 15:18:08 +01:00
|
|
|
_.set(acc, [type, resolverName], buildQuery(resolverName, resolverObj));
|
2020-02-10 19:13:01 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, acc);
|
|
|
|
}, {});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
generateSchema,
|
|
|
|
};
|