mirror of
https://github.com/strapi/strapi.git
synced 2025-07-29 03:50:26 +00:00
280 lines
7.2 KiB
JavaScript
280 lines
7.2 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Query.js service
|
|
*
|
|
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
|
*/
|
|
|
|
const _ = require('lodash');
|
|
const pluralize = require('pluralize');
|
|
const policyUtils = require('strapi-utils').policy;
|
|
|
|
module.exports = {
|
|
/**
|
|
* Convert parameters to valid filters parameters.
|
|
*
|
|
* @return Object
|
|
*/
|
|
|
|
convertToParams: params => {
|
|
return Object.keys(params).reduce((acc, current) => {
|
|
return Object.assign(acc, {
|
|
[`_${current}`]: params[current],
|
|
});
|
|
}, {});
|
|
},
|
|
|
|
/**
|
|
* Security to avoid infinite limit.
|
|
*
|
|
* @return String
|
|
*/
|
|
|
|
amountLimiting: params => {
|
|
if (params.limit && params.limit < 0) {
|
|
params.limit = 0;
|
|
} else if (params.limit && params.limit > strapi.plugins.graphql.config.amountLimit) {
|
|
params.limit = strapi.plugins.graphql.config.amountLimit;
|
|
}
|
|
|
|
return params;
|
|
},
|
|
|
|
/**
|
|
* Execute policies before the specified resolver.
|
|
*
|
|
* @return Promise or Error.
|
|
*/
|
|
|
|
composeQueryResolver: function(_schema, plugin, name, isSingular) {
|
|
const params = {
|
|
model: name,
|
|
};
|
|
|
|
const model = plugin
|
|
? strapi.plugins[plugin].models[name]
|
|
: strapi.models[name];
|
|
|
|
// Extract custom resolver or type description.
|
|
const { resolver: handler = {} } = _schema;
|
|
|
|
let queryName;
|
|
|
|
if (isSingular === 'force') {
|
|
queryName = name;
|
|
} else {
|
|
queryName = isSingular
|
|
? pluralize.singular(name)
|
|
: pluralize.plural(name);
|
|
}
|
|
|
|
// Retrieve policies.
|
|
const policies = _.get(handler, `Query.${queryName}.policies`, []);
|
|
|
|
// Retrieve resolverOf.
|
|
const resolverOf = _.get(handler, `Query.${queryName}.resolverOf`, '');
|
|
|
|
const policiesFn = [];
|
|
|
|
// Boolean to define if the resolver is going to be a resolver or not.
|
|
let isController = false;
|
|
|
|
// Retrieve resolver. It could be the custom resolver of the user
|
|
// or the shadow CRUD resolver (aka Content-Manager).
|
|
const resolver = (() => {
|
|
// Try to retrieve custom resolver.
|
|
const resolver = _.get(handler, `Query.${queryName}.resolver`);
|
|
|
|
if (_.isString(resolver) || _.isPlainObject(resolver)) {
|
|
const { handler = resolver } = _.isPlainObject(resolver)
|
|
? resolver
|
|
: {};
|
|
|
|
// Retrieve the controller's action to be executed.
|
|
const [name, action] = handler.split('.');
|
|
|
|
const controller = plugin
|
|
? _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`)
|
|
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
|
|
|
|
if (!controller) {
|
|
return new Error(
|
|
`Cannot find the controller's action ${name}.${action}`,
|
|
);
|
|
}
|
|
|
|
// We're going to return a controller instead.
|
|
isController = true;
|
|
|
|
// Push global policy to make sure the permissions will work as expected.
|
|
policiesFn.push(
|
|
policyUtils.globalPolicy(
|
|
undefined,
|
|
{
|
|
handler: `${name}.${action}`,
|
|
},
|
|
undefined,
|
|
plugin,
|
|
),
|
|
);
|
|
|
|
// Return the controller.
|
|
return controller;
|
|
} else if (resolver) {
|
|
// Function.
|
|
return resolver;
|
|
}
|
|
|
|
// We're going to return a controller instead.
|
|
isController = true;
|
|
|
|
const controllers = plugin
|
|
? strapi.plugins[plugin].controllers
|
|
: strapi.controllers;
|
|
|
|
// Try to find the controller that should be related to this model.
|
|
const controller = isSingular
|
|
? _.get(controllers, `${name}.findOne`)
|
|
: _.get(controllers, `${name}.find`);
|
|
|
|
if (!controller) {
|
|
return new Error(
|
|
`Cannot find the controller's action ${name}.${
|
|
isSingular ? 'findOne' : 'find'
|
|
}`,
|
|
);
|
|
}
|
|
|
|
// Push global policy to make sure the permissions will work as expected.
|
|
// We're trying to detect the controller name.
|
|
policiesFn.push(
|
|
policyUtils.globalPolicy(
|
|
undefined,
|
|
{
|
|
handler: `${name}.${isSingular ? 'findOne' : 'find'}`,
|
|
},
|
|
undefined,
|
|
plugin,
|
|
),
|
|
);
|
|
|
|
// Make the query compatible with our controller by
|
|
// setting in the context the parameters.
|
|
if (isSingular) {
|
|
return async (ctx, next) => {
|
|
ctx.params = {
|
|
...params,
|
|
[model.primaryKey]: ctx.params.id,
|
|
};
|
|
|
|
// Return the controller.
|
|
return controller(ctx, next);
|
|
};
|
|
}
|
|
|
|
// Plural.
|
|
return async (ctx, next) => {
|
|
ctx.params = this.amountLimiting(ctx.params);
|
|
ctx.query = Object.assign(
|
|
this.convertToParams(_.omit(ctx.params, 'where')),
|
|
ctx.params.where,
|
|
);
|
|
|
|
return controller(ctx, next);
|
|
};
|
|
})();
|
|
|
|
// The controller hasn't been found.
|
|
if (_.isError(resolver)) {
|
|
return resolver;
|
|
}
|
|
|
|
// Force policies of another action on a custom resolver.
|
|
if (_.isString(resolverOf) && !_.isEmpty(resolverOf)) {
|
|
// Retrieve the controller's action to be executed.
|
|
const [name, action] = resolverOf.split('.');
|
|
|
|
const controller = plugin
|
|
? _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`)
|
|
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
|
|
|
|
if (!controller) {
|
|
return new Error(
|
|
`Cannot find the controller's action ${name}.${action}`,
|
|
);
|
|
}
|
|
|
|
policiesFn[0] = policyUtils.globalPolicy(
|
|
undefined,
|
|
{
|
|
handler: `${name}.${action}`,
|
|
},
|
|
undefined,
|
|
plugin,
|
|
);
|
|
}
|
|
|
|
if (strapi.plugins['users-permissions']) {
|
|
policies.push('plugins.users-permissions.permissions');
|
|
}
|
|
|
|
// Populate policies.
|
|
policies.forEach(policy =>
|
|
policyUtils.get(
|
|
policy,
|
|
plugin,
|
|
policiesFn,
|
|
`GraphQL query "${queryName}"`,
|
|
name,
|
|
),
|
|
);
|
|
|
|
return async (obj, options, { context }) => {
|
|
// Hack to be able to handle permissions for each query.
|
|
const ctx = Object.assign(_.clone(context), {
|
|
request: Object.assign(_.clone(context.request), {
|
|
graphql: null,
|
|
}),
|
|
});
|
|
|
|
// Execute policies stack.
|
|
const policy = await strapi.koaMiddlewares.compose(policiesFn)(ctx);
|
|
|
|
// Policy doesn't always return errors but they update the current context.
|
|
if (
|
|
_.isError(ctx.request.graphql) ||
|
|
_.get(ctx.request.graphql, 'isBoom')
|
|
) {
|
|
return ctx.request.graphql;
|
|
}
|
|
|
|
// Something went wrong in the policy.
|
|
if (policy) {
|
|
return policy;
|
|
}
|
|
|
|
// Resolver can be a function. Be also a native resolver or a controller's action.
|
|
if (_.isFunction(resolver)) {
|
|
context.query = this.convertToParams(options);
|
|
context.params = this.amountLimiting(options);
|
|
|
|
if (isController) {
|
|
const values = await resolver.call(null, context);
|
|
|
|
if (ctx.body) {
|
|
return ctx.body;
|
|
}
|
|
|
|
return values && values.toJSON ? values.toJSON() : values;
|
|
}
|
|
|
|
return resolver.call(null, obj, options, context);
|
|
}
|
|
|
|
// Resolver can be a promise.
|
|
return resolver;
|
|
};
|
|
},
|
|
};
|