mirror of
https://github.com/strapi/strapi.git
synced 2025-11-07 21:58:23 +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 }) => ({
|
module.exports = ({ env }) => ({
|
||||||
graphql: {
|
graphql: {
|
||||||
amountLimit: 5,
|
amountLimit: 50,
|
||||||
depthLimit: 10,
|
depthLimit: 10,
|
||||||
apolloServer: {
|
apolloServer: {
|
||||||
tracing: true,
|
tracing: true,
|
||||||
|
|||||||
@ -50,9 +50,7 @@ module.exports = strapi => {
|
|||||||
});
|
});
|
||||||
_.merge(strapi, { api, plugins });
|
_.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 apisSchemas = Object.keys(strapi.api || {}).map(key => {
|
||||||
const schema = _.get(strapi.api[key], 'config.schema.graphql', {});
|
const schema = _.get(strapi.api[key], 'config.schema.graphql', {});
|
||||||
return attachMetadataToResolvers(schema, { api: key });
|
return attachMetadataToResolvers(schema, { api: key });
|
||||||
@ -68,10 +66,10 @@ module.exports = strapi => {
|
|||||||
return attachMetadataToResolvers(schema, { plugin: key });
|
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
|
// 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() {
|
initialize() {
|
||||||
|
|||||||
@ -494,8 +494,9 @@ const formatConnectionAggregator = function(fields, model, modelName) {
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const formatModelConnectionsGQL = function({ fields, model, name, resolver }) {
|
const formatModelConnectionsGQL = function({ fields, model: contentType, name, resolver }) {
|
||||||
const { globalId } = model;
|
const { globalId } = contentType;
|
||||||
|
const model = strapi.getModel(contentType.uid);
|
||||||
|
|
||||||
const connectionGlobalId = `${globalId}Connection`;
|
const connectionGlobalId = `${globalId}Connection`;
|
||||||
|
|
||||||
@ -514,8 +515,6 @@ const formatModelConnectionsGQL = function({ fields, model, name, resolver }) {
|
|||||||
}
|
}
|
||||||
modelConnectionTypes += groupByFormat.type;
|
modelConnectionTypes += groupByFormat.type;
|
||||||
|
|
||||||
const queryName = `${pluralName}Connection(sort: String, limit: Int, start: Int, where: JSON)`;
|
|
||||||
|
|
||||||
const connectionResolver = buildQueryResolver(`${pluralName}Connection.values`, resolver);
|
const connectionResolver = buildQueryResolver(`${pluralName}Connection.values`, resolver);
|
||||||
|
|
||||||
const connectionQueryName = `${pluralName}Connection`;
|
const connectionQueryName = `${pluralName}Connection`;
|
||||||
@ -524,7 +523,16 @@ const formatModelConnectionsGQL = function({ fields, model, name, resolver }) {
|
|||||||
globalId: connectionGlobalId,
|
globalId: connectionGlobalId,
|
||||||
definition: modelConnectionTypes,
|
definition: modelConnectionTypes,
|
||||||
query: {
|
query: {
|
||||||
[queryName]: connectionGlobalId,
|
[`${pluralName}Connection`]: {
|
||||||
|
args: {
|
||||||
|
sort: 'String',
|
||||||
|
limit: 'Int',
|
||||||
|
start: 'Int',
|
||||||
|
where: 'JSON',
|
||||||
|
...(resolver.args || {}),
|
||||||
|
},
|
||||||
|
type: connectionGlobalId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolvers: {
|
resolvers: {
|
||||||
Query: {
|
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;
|
ctx.request.body = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pass extra args as ctx.query
|
||||||
|
if (options.input) {
|
||||||
|
ctx.query = convertToParams(_.omit(options, 'input'));
|
||||||
|
}
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -71,14 +71,38 @@ const fieldsToSDL = ({ fields, configurations, model }) => {
|
|||||||
const operationToSDL = ({ fields, configurations }) => {
|
const operationToSDL = ({ fields, configurations }) => {
|
||||||
return Object.entries(fields)
|
return Object.entries(fields)
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
const [attr] = key.split('(');
|
const [attr] = key.split('(');
|
||||||
const attributeName = _.trim(attr);
|
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');
|
.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
|
* Applies description and deprecated to a field definition
|
||||||
* @param {string} definition field definition
|
* @param {string} definition field definition
|
||||||
|
|||||||
@ -12,8 +12,8 @@ const _ = require('lodash');
|
|||||||
const graphql = require('graphql');
|
const graphql = require('graphql');
|
||||||
const PublicationState = require('../types/publication-state');
|
const PublicationState = require('../types/publication-state');
|
||||||
const Types = require('./type-builder');
|
const Types = require('./type-builder');
|
||||||
const { buildModels } = require('./type-definitions');
|
const buildShadowCrud = require('./shadow-crud');
|
||||||
const { mergeSchemas, createDefaultSchema, diffResolvers } = require('./utils');
|
const { createDefaultSchema, diffResolvers } = require('./utils');
|
||||||
const { toSDL } = require('./schema-definitions');
|
const { toSDL } = require('./schema-definitions');
|
||||||
const { buildQuery, buildMutation } = require('./resolvers-builder');
|
const { buildQuery, buildMutation } = require('./resolvers-builder');
|
||||||
|
|
||||||
@ -27,11 +27,15 @@ const generateSchema = () => {
|
|||||||
const isFederated = _.get(strapi.plugins.graphql.config, 'federation', false);
|
const isFederated = _.get(strapi.plugins.graphql.config, 'federation', false);
|
||||||
const shadowCRUDEnabled = strapi.plugins.graphql.config.shadowCRUD !== 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 _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.
|
// Extract custom definition, query or resolver.
|
||||||
const { definition, query, mutation, resolver = {} } = _schema;
|
const { definition, query, mutation, resolver = {} } = _schema;
|
||||||
|
|
||||||
@ -80,10 +84,12 @@ const generateSchema = () => {
|
|||||||
firstname: String!
|
firstname: String!
|
||||||
lastname: String!
|
lastname: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
${queryFields}
|
${queryFields}
|
||||||
${query}
|
${query}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
${mutationFields}
|
${mutationFields}
|
||||||
${mutation}
|
${mutation}
|
||||||
@ -133,14 +139,6 @@ const writeGenerateSchema = schema => {
|
|||||||
return strapi.fs.writeAppFile('exports/graphql/schema.graphql', printSchema);
|
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 => {
|
const buildResolvers = resolvers => {
|
||||||
// Transform object to only contain function.
|
// Transform object to only contain function.
|
||||||
return Object.keys(resolvers).reduce((acc, type) => {
|
return Object.keys(resolvers).reduce((acc, type) => {
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* GraphQL.js service
|
|
||||||
*
|
|
||||||
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { contentTypes } = require('strapi-utils');
|
const { contentTypes } = require('strapi-utils');
|
||||||
|
|
||||||
@ -18,16 +12,46 @@ const DynamicZoneScalar = require('../types/dynamiczoneScalar');
|
|||||||
|
|
||||||
const { formatModelConnectionsGQL } = require('./build-aggregation');
|
const { formatModelConnectionsGQL } = require('./build-aggregation');
|
||||||
const types = require('./type-builder');
|
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 { toSDL, getTypeDescription } = require('./schema-definitions');
|
||||||
const { toSingular, toPlural } = require('./naming');
|
const { toSingular, toPlural } = require('./naming');
|
||||||
const { buildQuery, buildMutation } = require('./resolvers-builder');
|
const { buildQuery, buildMutation } = require('./resolvers-builder');
|
||||||
const { actionExists } = require('./utils');
|
|
||||||
|
|
||||||
const OPTIONS = Symbol();
|
const OPTIONS = Symbol();
|
||||||
|
|
||||||
const FIND_QUERY_ARGUMENTS = `(sort: String, limit: Int, start: Int, where: JSON, publicationState: PublicationState)`;
|
const FIND_QUERY_ARGUMENTS = {
|
||||||
const FIND_ONE_QUERY_ARGUMENTS = `(id: ID!, publicationState: PublicationState)`;
|
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) => {
|
const assignOptions = (element, parent) => {
|
||||||
if (Array.isArray(element)) {
|
if (Array.isArray(element)) {
|
||||||
@ -41,10 +65,18 @@ const isQueryEnabled = (schema, name) => {
|
|||||||
return _.get(schema, `resolver.Query.${name}`) !== false;
|
return _.get(schema, `resolver.Query.${name}`) !== false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getQueryInfo = (schema, name) => {
|
||||||
|
return _.get(schema, `resolver.Query.${name}`, {});
|
||||||
|
};
|
||||||
|
|
||||||
const isMutationEnabled = (schema, name) => {
|
const isMutationEnabled = (schema, name) => {
|
||||||
return _.get(schema, `resolver.Mutation.${name}`) !== false;
|
return _.get(schema, `resolver.Mutation.${name}`) !== false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMutationInfo = (schema, name) => {
|
||||||
|
return _.get(schema, `resolver.Mutation.${name}`, {});
|
||||||
|
};
|
||||||
|
|
||||||
const isNotPrivate = _.curry((model, attributeName) => {
|
const isNotPrivate = _.curry((model, attributeName) => {
|
||||||
return !contentTypes.isPrivateAttribute(model, attributeName);
|
return !contentTypes.isPrivateAttribute(model, attributeName);
|
||||||
});
|
});
|
||||||
@ -294,19 +326,19 @@ const buildAssocResolvers = model => {
|
|||||||
*
|
*
|
||||||
* @return Object
|
* @return Object
|
||||||
*/
|
*/
|
||||||
const buildModels = models => {
|
const buildModels = (models, ctx) => {
|
||||||
return models.map(model => {
|
return models.map(model => {
|
||||||
const { kind, modelType } = model;
|
const { kind, modelType } = model;
|
||||||
|
|
||||||
if (modelType === 'component') {
|
if (modelType === 'component') {
|
||||||
return buildComponent(model);
|
return buildComponent(model, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'singleType':
|
case 'singleType':
|
||||||
return buildSingleType(model);
|
return buildSingleType(model, ctx);
|
||||||
default:
|
default:
|
||||||
return buildCollectionType(model);
|
return buildCollectionType(model, ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -353,14 +385,12 @@ const buildComponent = component => {
|
|||||||
return schema;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildSingleType = model => {
|
const buildSingleType = (model, ctx) => {
|
||||||
const { uid, modelName } = model;
|
const { uid, modelName } = model;
|
||||||
|
|
||||||
const singularName = toSingular(modelName);
|
const singularName = toSingular(modelName);
|
||||||
|
|
||||||
const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
|
const globalType = _.get(ctx.schema, `type.${model.globalId}`, {});
|
||||||
|
|
||||||
const globalType = _.get(_schema, `type.${model.globalId}`, {});
|
|
||||||
|
|
||||||
const localSchema = buildModelDefinition(model, globalType);
|
const localSchema = buildModelDefinition(model, globalType);
|
||||||
|
|
||||||
@ -369,22 +399,32 @@ const buildSingleType = model => {
|
|||||||
return localSchema;
|
return localSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQueryEnabled(_schema, singularName)) {
|
if (isQueryEnabled(ctx.schema, singularName)) {
|
||||||
const resolver = buildQuery(singularName, {
|
const resolverOpts = {
|
||||||
resolver: `${uid}.find`,
|
resolver: `${uid}.find`,
|
||||||
..._.get(_schema, `resolver.Query.${singularName}`, {}),
|
...getQueryInfo(ctx.schema, singularName),
|
||||||
});
|
};
|
||||||
|
|
||||||
_.merge(localSchema, {
|
const resolver = buildQuery(singularName, resolverOpts);
|
||||||
|
|
||||||
|
const query = {
|
||||||
query: {
|
query: {
|
||||||
[`${singularName}(publicationState: PublicationState)`]: model.globalId,
|
[singularName]: {
|
||||||
|
args: {
|
||||||
|
publicationState: 'PublicationState',
|
||||||
|
...(resolverOpts.args || {}),
|
||||||
|
},
|
||||||
|
type: model.globalId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolvers: {
|
resolvers: {
|
||||||
Query: {
|
Query: {
|
||||||
[singularName]: wrapPublicationStateResolver(resolver),
|
[singularName]: wrapPublicationStateResolver(resolver),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
_.merge(localSchema, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add model Input definition.
|
// Add model Input definition.
|
||||||
@ -392,7 +432,7 @@ const buildSingleType = model => {
|
|||||||
|
|
||||||
// build every mutation
|
// build every mutation
|
||||||
['update', 'delete'].forEach(action => {
|
['update', 'delete'].forEach(action => {
|
||||||
const mutationSchema = buildMutationTypeDef({ model, action }, { _schema });
|
const mutationSchema = buildMutationTypeDef({ model, action }, ctx);
|
||||||
|
|
||||||
mergeSchemas(localSchema, mutationSchema);
|
mergeSchemas(localSchema, mutationSchema);
|
||||||
});
|
});
|
||||||
@ -400,15 +440,13 @@ const buildSingleType = model => {
|
|||||||
return localSchema;
|
return localSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildCollectionType = model => {
|
const buildCollectionType = (model, ctx) => {
|
||||||
const { plugin, modelName, uid } = model;
|
const { plugin, modelName, uid } = model;
|
||||||
|
|
||||||
const singularName = toSingular(modelName);
|
const singularName = toSingular(modelName);
|
||||||
const pluralName = toPlural(modelName);
|
const pluralName = toPlural(modelName);
|
||||||
|
|
||||||
const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
|
const globalType = _.get(ctx.schema, `type.${model.globalId}`, {});
|
||||||
|
|
||||||
const globalType = _.get(_schema, `type.${model.globalId}`, {});
|
|
||||||
|
|
||||||
const localSchema = buildModelDefinition(model, globalType);
|
const localSchema = buildModelDefinition(model, globalType);
|
||||||
const { typeDefObj } = localSchema;
|
const { typeDefObj } = localSchema;
|
||||||
@ -418,46 +456,65 @@ const buildCollectionType = model => {
|
|||||||
return localSchema;
|
return localSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQueryEnabled(_schema, singularName)) {
|
if (isQueryEnabled(ctx.schema, singularName)) {
|
||||||
const resolverOpts = {
|
const resolverOpts = {
|
||||||
resolver: `${uid}.findOne`,
|
resolver: `${uid}.findOne`,
|
||||||
..._.get(_schema, `resolver.Query.${singularName}`, {}),
|
...getQueryInfo(ctx.schema, singularName),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (actionExists(resolverOpts)) {
|
if (actionExists(resolverOpts)) {
|
||||||
const resolver = buildQuery(singularName, resolverOpts);
|
const resolver = buildQuery(singularName, resolverOpts);
|
||||||
_.merge(localSchema, {
|
|
||||||
|
const query = {
|
||||||
query: {
|
query: {
|
||||||
// TODO: support all the unique fields
|
[singularName]: {
|
||||||
[`${singularName}${FIND_ONE_QUERY_ARGUMENTS}`]: model.globalId,
|
args: {
|
||||||
|
...FIND_ONE_QUERY_ARGUMENTS,
|
||||||
|
...(resolverOpts.args || {}),
|
||||||
|
},
|
||||||
|
type: model.globalId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolvers: {
|
resolvers: {
|
||||||
Query: {
|
Query: {
|
||||||
[singularName]: wrapPublicationStateResolver(resolver),
|
[singularName]: wrapPublicationStateResolver(resolver),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
_.merge(localSchema, query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isQueryEnabled(_schema, pluralName)) {
|
if (isQueryEnabled(ctx.schema, pluralName)) {
|
||||||
const resolverOpts = {
|
const resolverOpts = {
|
||||||
resolver: `${uid}.find`,
|
resolver: `${uid}.find`,
|
||||||
..._.get(_schema, `resolver.Query.${pluralName}`, {}),
|
...getQueryInfo(ctx.schema, pluralName),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (actionExists(resolverOpts)) {
|
if (actionExists(resolverOpts)) {
|
||||||
const resolver = buildQuery(pluralName, resolverOpts);
|
const resolver = buildQuery(pluralName, resolverOpts);
|
||||||
_.merge(localSchema, {
|
|
||||||
|
const query = {
|
||||||
query: {
|
query: {
|
||||||
[`${pluralName}${FIND_QUERY_ARGUMENTS}`]: `[${model.globalId}]`,
|
[pluralName]: {
|
||||||
|
args: {
|
||||||
|
...FIND_QUERY_ARGUMENTS,
|
||||||
|
...(resolverOpts.args || {}),
|
||||||
|
},
|
||||||
|
type: `[${model.globalId}]`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolvers: {
|
resolvers: {
|
||||||
Query: {
|
Query: {
|
||||||
[pluralName]: wrapPublicationStateResolver(resolver),
|
[pluralName]: wrapPublicationStateResolver(resolver),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
if (isQueryEnabled(_schema, `${pluralName}Connection`)) {
|
_.merge(localSchema, query);
|
||||||
|
|
||||||
|
if (isQueryEnabled(ctx.schema, `${pluralName}Connection`)) {
|
||||||
// Generate the aggregation for the given model
|
// Generate the aggregation for the given model
|
||||||
const aggregationSchema = formatModelConnectionsGQL({
|
const aggregationSchema = formatModelConnectionsGQL({
|
||||||
fields: typeDefObj,
|
fields: typeDefObj,
|
||||||
@ -477,7 +534,7 @@ const buildCollectionType = model => {
|
|||||||
|
|
||||||
// build every mutation
|
// build every mutation
|
||||||
['create', 'update', 'delete'].forEach(action => {
|
['create', 'update', 'delete'].forEach(action => {
|
||||||
const mutationSchema = buildMutationTypeDef({ model, action }, { _schema });
|
const mutationSchema = buildMutationTypeDef({ model, action }, ctx);
|
||||||
mergeSchemas(localSchema, mutationSchema);
|
mergeSchemas(localSchema, mutationSchema);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -487,14 +544,14 @@ const buildCollectionType = model => {
|
|||||||
// TODO:
|
// TODO:
|
||||||
// - Implement batch methods (need to update the content-manager as well).
|
// - Implement batch methods (need to update the content-manager as well).
|
||||||
// - Implement nested transactional methods (create/update).
|
// - Implement nested transactional methods (create/update).
|
||||||
const buildMutationTypeDef = ({ model, action }, { _schema }) => {
|
const buildMutationTypeDef = ({ model, action }, ctx) => {
|
||||||
const capitalizedName = _.upperFirst(toSingular(model.modelName));
|
const capitalizedName = _.upperFirst(toSingular(model.modelName));
|
||||||
const mutationName = `${action}${capitalizedName}`;
|
const mutationName = `${action}${capitalizedName}`;
|
||||||
|
|
||||||
const resolverOpts = {
|
const resolverOpts = {
|
||||||
resolver: `${model.uid}.${action}`,
|
resolver: `${model.uid}.${action}`,
|
||||||
transformOutput: result => ({ [toSingular(model.modelName)]: result }),
|
transformOutput: result => ({ [toSingular(model.modelName)]: result }),
|
||||||
..._.get(_schema, `resolver.Mutation.${mutationName}`, {}),
|
...getMutationInfo(ctx.schema, mutationName),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!actionExists(resolverOpts)) {
|
if (!actionExists(resolverOpts)) {
|
||||||
@ -509,7 +566,7 @@ const buildMutationTypeDef = ({ model, action }, { _schema }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ignore if disabled
|
// ignore if disabled
|
||||||
if (!isMutationEnabled(_schema, mutationName)) {
|
if (!isMutationEnabled(ctx.schema, mutationName)) {
|
||||||
return {
|
return {
|
||||||
definition,
|
definition,
|
||||||
};
|
};
|
||||||
@ -517,15 +574,24 @@ const buildMutationTypeDef = ({ model, action }, { _schema }) => {
|
|||||||
|
|
||||||
const { kind } = model;
|
const { kind } = model;
|
||||||
|
|
||||||
let mutationDef = `${mutationName}(input: ${mutationName}Input)`;
|
const args = {};
|
||||||
if (kind === 'singleType' && action === 'delete') {
|
|
||||||
mutationDef = mutationName;
|
if (kind !== 'singleType' || action !== 'delete') {
|
||||||
|
Object.assign(args, {
|
||||||
|
input: `${mutationName}Input`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
definition,
|
definition,
|
||||||
mutation: {
|
mutation: {
|
||||||
[mutationDef]: `${mutationName}Payload`,
|
[mutationName]: {
|
||||||
|
args: {
|
||||||
|
...args,
|
||||||
|
...(resolverOpts.args || {}),
|
||||||
|
},
|
||||||
|
type: `${mutationName}Payload`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolvers: {
|
resolvers: {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
@ -535,6 +601,4 @@ const buildMutationTypeDef = ({ model, action }, { _schema }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = buildShadowCrud;
|
||||||
buildModels,
|
|
||||||
};
|
|
||||||
@ -4,24 +4,39 @@ const _ = require('lodash');
|
|||||||
const { QUERY_OPERATORS } = require('strapi-utils');
|
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;
|
if (_.isEmpty(sub)) return;
|
||||||
const { definition = '', query = {}, mutation = {}, resolvers = {} } = sub;
|
const { definition = '', query = {}, mutation = {}, resolvers = {} } = sub;
|
||||||
|
|
||||||
root.definition += '\n' + definition;
|
object.definition += '\n' + definition;
|
||||||
_.merge(root, {
|
_.merge(object, {
|
||||||
query,
|
query,
|
||||||
mutation,
|
mutation,
|
||||||
resolvers,
|
resolvers,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return root;
|
return object;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an empty schema
|
||||||
|
* @returns {Schema}
|
||||||
|
*/
|
||||||
const createDefaultSchema = () => ({
|
const createDefaultSchema = () => ({
|
||||||
definition: '',
|
definition: '',
|
||||||
query: {},
|
query: {},
|
||||||
|
|||||||
@ -205,6 +205,20 @@ const addCreateLocalizationAction = contentType => {
|
|||||||
_.set(strapi, coreApiControllerPath, handler);
|
_.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
|
* Add localization mutation & filters to use with the graphql plugin
|
||||||
* @param {object} contentType
|
* @param {object} contentType
|
||||||
@ -216,27 +230,61 @@ const addGraphqlLocalizationAction = contentType => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: {
|
||||||
|
[`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 typeName = _.capitalize(modelName);
|
||||||
_.mergeWith(
|
const mutationName = `create${typeName}Localization`;
|
||||||
strapi.plugins.i18n.config.schema.graphql,
|
const mutationDef = `${mutationName}(input: update${typeName}Input!): ${typeName}!`;
|
||||||
{
|
const actionName = `${contentType.uid}.createLocalization`;
|
||||||
mutation: `
|
|
||||||
create${typeName}Localization(input: update${typeName}Input!): ${typeName}!
|
addGraphqlSchema({
|
||||||
`,
|
mutation: mutationDef,
|
||||||
resolver: {
|
resolver: {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
[`create${typeName}Localization`]: {
|
[mutationName]: {
|
||||||
resolver: `application::${modelName}.${modelName}.createLocalization`,
|
resolver: actionName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
(dest, src) => {
|
|
||||||
if (typeof dest === 'string') {
|
|
||||||
return `${dest}\n${src}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user