2018-09-10 16:05:00 +08:00
|
|
|
/**
|
2020-02-10 19:13:01 +01:00
|
|
|
* Build queries and mutation resolvers
|
2018-09-10 16:05:00 +08:00
|
|
|
*/
|
|
|
|
|
2020-02-10 19:13:01 +01:00
|
|
|
'use strict';
|
|
|
|
|
2018-09-10 16:05:00 +08:00
|
|
|
const _ = require('lodash');
|
2020-01-29 15:30:53 +01:00
|
|
|
const compose = require('koa-compose');
|
|
|
|
|
2020-02-10 19:13:01 +01:00
|
|
|
const { convertToParams, convertToQuery, amountLimiting } = require('./utils');
|
|
|
|
const { policy: policyUtils } = require('strapi-utils');
|
2019-03-13 19:27:18 +01:00
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
const buildMutation = (mutationName, config) => {
|
|
|
|
const { resolver, resolverOf, transformOutput = _.identity } = config;
|
|
|
|
|
|
|
|
if (_.isFunction(resolver) && !isResolvablePath(resolverOf)) {
|
|
|
|
throw new Error(
|
|
|
|
`Cannot create mutation "${mutationName}". Missing "resolverOf" option with custom resolver.`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const policiesMiddleware = compose(getPolicies(config));
|
|
|
|
|
|
|
|
// custom resolvers
|
|
|
|
if (_.isFunction(resolver)) {
|
|
|
|
return async (root, options = {}, graphqlContext) => {
|
|
|
|
const ctx = buildMutationContext({ options, graphqlContext });
|
|
|
|
|
|
|
|
await policiesMiddleware(ctx);
|
2020-03-20 22:24:14 +09:00
|
|
|
graphqlContext.context = ctx;
|
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
return resolver(root, options, graphqlContext);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const action = getAction(resolver);
|
|
|
|
|
|
|
|
return async (root, options = {}, graphqlContext) => {
|
|
|
|
const ctx = buildMutationContext({ options, graphqlContext });
|
|
|
|
|
|
|
|
await policiesMiddleware(ctx);
|
|
|
|
|
2020-02-11 17:35:09 +01:00
|
|
|
const values = await action(ctx);
|
2020-01-29 15:30:53 +01:00
|
|
|
const result = ctx.body || values;
|
|
|
|
|
|
|
|
if (_.isError(result)) {
|
|
|
|
throw result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return transformOutput(result);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildMutationContext = ({ options, graphqlContext }) => {
|
|
|
|
const { context } = graphqlContext;
|
|
|
|
|
2020-03-20 22:24:14 +09:00
|
|
|
const ctx = context.app.createContext(_.clone(context.req), _.clone(context.res));
|
2020-02-11 17:35:09 +01:00
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
if (options.input && options.input.where) {
|
2020-02-11 17:35:09 +01:00
|
|
|
ctx.params = convertToParams(options.input.where || {});
|
2020-01-29 15:30:53 +01:00
|
|
|
} else {
|
2020-02-11 17:35:09 +01:00
|
|
|
ctx.params = {};
|
2020-01-29 15:30:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options.input && options.input.data) {
|
2020-02-11 17:35:09 +01:00
|
|
|
ctx.request.body = options.input.data || {};
|
2020-01-29 15:30:53 +01:00
|
|
|
} else {
|
2020-02-11 17:35:09 +01:00
|
|
|
ctx.request.body = options;
|
2020-01-29 15:30:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return ctx;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildQuery = (queryName, config) => {
|
2020-02-10 12:50:11 +01:00
|
|
|
const { resolver } = config;
|
2020-01-29 15:30:53 +01:00
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
try {
|
|
|
|
validateResolverOption(config);
|
|
|
|
} catch (error) {
|
|
|
|
throw new Error(`Cannot create query "${queryName}": ${error.message}`);
|
2020-01-29 15:30:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const policiesMiddleware = compose(getPolicies(config));
|
|
|
|
|
|
|
|
// custom resolvers
|
|
|
|
if (_.isFunction(resolver)) {
|
|
|
|
return async (root, options = {}, graphqlContext) => {
|
|
|
|
const { ctx, opts } = buildQueryContext({ options, graphqlContext });
|
|
|
|
|
|
|
|
await policiesMiddleware(ctx);
|
2020-03-20 22:24:14 +09:00
|
|
|
graphqlContext.context = ctx;
|
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
return resolver(root, opts, graphqlContext);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const action = getAction(resolver);
|
|
|
|
|
|
|
|
return async (root, options = {}, graphqlContext) => {
|
|
|
|
const { ctx } = buildQueryContext({ options, graphqlContext });
|
|
|
|
|
|
|
|
// duplicate context
|
|
|
|
await policiesMiddleware(ctx);
|
|
|
|
|
|
|
|
const values = await action(ctx);
|
|
|
|
const result = ctx.body || values;
|
|
|
|
|
|
|
|
if (_.isError(result)) {
|
|
|
|
throw result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
const validateResolverOption = config => {
|
|
|
|
const { resolver, resolverOf, policies } = config;
|
|
|
|
|
|
|
|
if (_.isFunction(resolver) && !isResolvablePath(resolverOf)) {
|
|
|
|
throw new Error(`Missing "resolverOf" option with custom resolver.`);
|
|
|
|
}
|
|
|
|
|
2020-03-20 22:24:14 +09:00
|
|
|
if (!_.isUndefined(policies) && (!Array.isArray(policies) || !_.every(policies, _.isString))) {
|
2020-02-10 12:50:11 +01:00
|
|
|
throw new Error('Policies option must be an array of string.');
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
const buildQueryContext = ({ options, graphqlContext }) => {
|
|
|
|
const { context } = graphqlContext;
|
|
|
|
const _options = _.cloneDeep(options);
|
|
|
|
|
2020-03-20 22:24:14 +09:00
|
|
|
const ctx = context.app.createContext(_.clone(context.req), _.clone(context.res));
|
2020-01-29 15:30:53 +01:00
|
|
|
|
|
|
|
// Note: we've to used the Object.defineProperties to reset the prototype. It seems that the cloning the context
|
|
|
|
// cause a lost of the Object prototype.
|
|
|
|
const opts = amountLimiting(_options);
|
|
|
|
|
|
|
|
ctx.query = {
|
|
|
|
...convertToParams(_.omit(opts, 'where')),
|
|
|
|
...convertToQuery(opts.where),
|
|
|
|
};
|
|
|
|
|
|
|
|
ctx.params = convertToParams(opts);
|
|
|
|
|
|
|
|
return { ctx, opts };
|
|
|
|
};
|
|
|
|
|
|
|
|
const getAction = resolver => {
|
|
|
|
if (!_.isString(resolver)) {
|
|
|
|
throw new Error(`Error building query. Expected a string, got ${resolver}`);
|
|
|
|
}
|
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
const actionDetails = getActionDetails(resolver);
|
|
|
|
const actionFn = getActionFn(actionDetails);
|
2020-01-29 15:30:53 +01:00
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
if (!actionFn) {
|
|
|
|
throw new Error(
|
|
|
|
`[GraphQL] Cannot find action "${resolver}". Check your graphql configurations.`
|
|
|
|
);
|
2020-01-29 15:30:53 +01:00
|
|
|
}
|
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
return actionFn;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getActionFn = details => {
|
|
|
|
const { controller, action, plugin, api } = details;
|
|
|
|
|
|
|
|
if (plugin) {
|
2020-03-20 22:24:14 +09:00
|
|
|
return _.get(strapi.plugins, [_.toLower(plugin), 'controllers', _.toLower(controller), action]);
|
2020-01-29 15:30:53 +01:00
|
|
|
}
|
|
|
|
|
2020-03-20 22:24:14 +09:00
|
|
|
return _.get(strapi.api, [_.toLower(api), 'controllers', _.toLower(controller), action]);
|
2020-01-29 15:30:53 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
const getActionDetails = resolver => {
|
|
|
|
if (resolver.startsWith('plugins::')) {
|
|
|
|
const [, path] = resolver.split('::');
|
|
|
|
const [plugin, controller, action] = path.split('.');
|
|
|
|
|
|
|
|
return { plugin, controller, action };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resolver.startsWith('application::')) {
|
|
|
|
const [, path] = resolver.split('::');
|
|
|
|
const [api, controller, action] = path.split('.');
|
|
|
|
|
|
|
|
return { api, controller, action };
|
|
|
|
}
|
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
const args = resolver.split('.');
|
2020-01-29 15:30:53 +01:00
|
|
|
|
2020-02-10 12:50:11 +01:00
|
|
|
if (args.length === 3) {
|
|
|
|
const [api, controller, action] = args;
|
|
|
|
return { api, controller, action };
|
|
|
|
}
|
|
|
|
|
|
|
|
// if direct api access
|
|
|
|
if (args.length === 2) {
|
|
|
|
const [controller, action] = args;
|
|
|
|
return { api: controller, controller, action };
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(
|
|
|
|
`[GraphQL] Could not find action for resolver "${resolver}". Check your graphql configurations.`
|
|
|
|
);
|
2020-01-29 15:30:53 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if a resolverPath (resolver or resovlerOf) might be resolved
|
|
|
|
*/
|
|
|
|
const isResolvablePath = path => _.isString(path) && !_.isEmpty(path);
|
|
|
|
|
2020-01-30 10:49:42 +01:00
|
|
|
const getPolicies = config => {
|
2020-01-29 15:30:53 +01:00
|
|
|
const { resolver, policies = [], resolverOf } = config;
|
|
|
|
|
2020-01-30 10:49:42 +01:00
|
|
|
const { api, plugin } = config['_metadatas'] || {};
|
|
|
|
|
2020-01-29 15:30:53 +01:00
|
|
|
const policyFns = [];
|
|
|
|
|
2020-03-20 22:24:14 +09:00
|
|
|
const { controller, action, plugin: pathPlugin } = isResolvablePath(resolverOf)
|
2020-01-29 15:30:53 +01:00
|
|
|
? getActionDetails(resolverOf)
|
|
|
|
: getActionDetails(resolver);
|
|
|
|
|
|
|
|
const globalPolicy = policyUtils.globalPolicy({
|
|
|
|
controller,
|
|
|
|
action,
|
2020-01-30 10:49:42 +01:00
|
|
|
plugin: pathPlugin,
|
2020-01-29 15:30:53 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
policyFns.push(globalPolicy);
|
|
|
|
|
|
|
|
if (strapi.plugins['users-permissions']) {
|
2020-02-10 12:50:11 +01:00
|
|
|
policies.unshift('plugins::users-permissions.permissions');
|
2020-01-29 15:30:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
policies.forEach(policy => {
|
|
|
|
const policyFn = policyUtils.get(policy, plugin, api);
|
|
|
|
policyFns.push(policyFn);
|
|
|
|
});
|
|
|
|
|
|
|
|
return policyFns;
|
|
|
|
};
|
|
|
|
|
2020-01-30 10:49:42 +01:00
|
|
|
module.exports = {
|
|
|
|
buildQuery,
|
2020-01-31 17:40:02 +01:00
|
|
|
buildMutation,
|
2020-01-30 10:49:42 +01:00
|
|
|
};
|