177 lines
4.6 KiB
JavaScript
Raw Normal View History

'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.
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');
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 AdminUser {
id: ID!
username: String
Hide creator fields from public api by default (#8052) * Add model option to hide/show creators fields in public API response Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Add content-types util, rework sanitize-entity's private handling Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Update search e2e tests, fix an issue on empty search for the core-api controller (find) Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix GraphQL plugin (handle privates attributes on typeDefs + resolver builds) Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix sanitizeEntity import Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Move doc update from beta to stable Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix e2e test Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix pr comments Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Remove creator's field from upload controller routes Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix typedef build for graphql association Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix pr (comments + several issues) Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Add tests for search behavior in content-manager Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Rename files variables to meaningful names (upload controllers) Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> * Fix test with search id matching serialNumber Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com> * Add toHasBeenCalledWith check for config.get (utils/content-types.test.js) Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu> Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
2020-10-01 17:47:08 +02:00
firstname: String!
lastname: String!
}
type Query {
${queryFields}
${query}
}
type Mutation {
${mutationFields}
${mutation}
}
${scalarDef}
`;
// // Build schema.
if (strapi.config.environment !== 'production') {
// Write schema.
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
writeGenerateSchema(graphql.printSchema(schema));
}
// Remove custom scalar (like Upload), if not using Federation
// Federation requires scalar Upload defined in typeDefs to use
// buildFederatedSchema()
// (https://www.apollographql.com/docs/apollo-server/federation/implementing-services/)
const isFederated = _.get(strapi.plugins.graphql, 'config.federation', false);
if (!isFederated) {
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 = () => {
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,
};