| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | '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; | 
					
						
							| 
									
										
										
										
											2019-06-08 18:50:07 +02:00
										 |  |  | const compose = require('koa-compose'); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Convert parameters to valid filters parameters. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return Object | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-16 22:53:54 +02:00
										 |  |  |   convertToParams: params => { | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     return Object.keys(params).reduce((acc, current) => { | 
					
						
							| 
									
										
										
										
											2019-09-16 22:53:54 +02:00
										 |  |  |       const key = current === 'id' ? 'id' : `_${current}`; | 
					
						
							| 
									
										
										
										
											2019-03-15 21:03:22 +01:00
										 |  |  |       acc[key] = params[current]; | 
					
						
							|  |  |  |       return acc; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     }, {}); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-02 13:30:38 +01:00
										 |  |  |   convertToQuery: function(params) { | 
					
						
							|  |  |  |     const result = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     _.forEach(params, (value, key) => { | 
					
						
							|  |  |  |       if (_.isPlainObject(value)) { | 
					
						
							|  |  |  |         const flatObject = this.convertToQuery(value); | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |         _.forEach(flatObject, (_value, _key) => { | 
					
						
							| 
									
										
										
										
											2019-02-02 13:30:38 +01:00
										 |  |  |           result[`${key}.${_key}`] = _value; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         result[key] = value; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Security to avoid infinite limit. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return String | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-06 18:11:53 +01:00
										 |  |  |   amountLimiting: (params = {}) => { | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |     const { amountLimit } = strapi.plugins.graphql.config; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-23 11:20:57 +02:00
										 |  |  |     if (!amountLimit) return params; | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-22 14:45:09 +05:00
										 |  |  |     if (!params.limit || params.limit === -1 || params.limit > amountLimit) { | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |       params.limit = amountLimit; | 
					
						
							|  |  |  |     } else if (params.limit < 0) { | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       params.limit = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return params; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Execute policies before the specified resolver. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return Promise or Error. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-11 21:55:26 +08:00
										 |  |  |   composeQueryResolver: function({ _schema, plugin, name, isSingular }) { | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     const params = { | 
					
						
							|  |  |  |       model: name, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Extract custom resolver or type description.
 | 
					
						
							|  |  |  |     const { resolver: handler = {} } = _schema; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let queryName; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isSingular === 'force') { | 
					
						
							|  |  |  |       queryName = name; | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |       queryName = isSingular | 
					
						
							|  |  |  |         ? pluralize.singular(name) | 
					
						
							|  |  |  |         : pluralize.plural(name); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-02 17:01:04 +08:00
										 |  |  |     // Retrieve policies.
 | 
					
						
							|  |  |  |     const policies = _.get(handler, `Query.${queryName}.policies`, []); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     // Retrieve resolverOf.
 | 
					
						
							|  |  |  |     const resolverOf = _.get(handler, `Query.${queryName}.resolverOf`, ''); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const policiesFn = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     // Boolean to define if the resolver is going to be a controller or not.
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     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)) { | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |         const { handler = resolver } = _.isPlainObject(resolver) | 
					
						
							|  |  |  |           ? resolver | 
					
						
							|  |  |  |           : {}; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Retrieve the controller's action to be executed.
 | 
					
						
							|  |  |  |         const [name, action] = handler.split('.'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const controller = plugin | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |           ? _.get( | 
					
						
							|  |  |  |               strapi.plugins, | 
					
						
							|  |  |  |               `${plugin}.controllers.${_.toLower(name)}.${action}` | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |           : _.get(strapi.controllers, `${_.toLower(name)}.${action}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!controller) { | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |           return new Error( | 
					
						
							|  |  |  |             `Cannot find the controller's action ${name}.${action}` | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 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, | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |             plugin | 
					
						
							|  |  |  |           ) | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Return the controller.
 | 
					
						
							|  |  |  |         return controller; | 
					
						
							|  |  |  |       } else if (resolver) { | 
					
						
							|  |  |  |         // Function.
 | 
					
						
							|  |  |  |         return resolver; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // We're going to return a controller instead.
 | 
					
						
							|  |  |  |       isController = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |       const controllers = plugin | 
					
						
							|  |  |  |         ? strapi.plugins[plugin].controllers | 
					
						
							|  |  |  |         : strapi.controllers; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // 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( | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |           `Cannot find the controller's action ${name}.${ | 
					
						
							|  |  |  |             isSingular ? 'findOne' : 'find' | 
					
						
							|  |  |  |           }`
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // 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, | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |           plugin | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Make the query compatible with our controller by
 | 
					
						
							|  |  |  |       // setting in the context the parameters.
 | 
					
						
							|  |  |  |       if (isSingular) { | 
					
						
							|  |  |  |         return async (ctx, next) => { | 
					
						
							|  |  |  |           ctx.params = { | 
					
						
							|  |  |  |             ...params, | 
					
						
							| 
									
										
										
										
											2019-09-16 22:53:54 +02:00
										 |  |  |             id: ctx.query.id, | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |           }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Return the controller.
 | 
					
						
							|  |  |  |           return controller(ctx, next); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Plural.
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:16:28 +01:00
										 |  |  |       return controller; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     })(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 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 | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |         ? _.get( | 
					
						
							|  |  |  |             strapi.plugins, | 
					
						
							|  |  |  |             `${plugin}.controllers.${_.toLower(name)}.${action}` | 
					
						
							|  |  |  |           ) | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |         : _.get(strapi.controllers, `${_.toLower(name)}.${action}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!controller) { | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |         return new Error( | 
					
						
							|  |  |  |           `Cannot find the controller's action ${name}.${action}` | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       policiesFn[0] = policyUtils.globalPolicy( | 
					
						
							|  |  |  |         undefined, | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           handler: `${name}.${action}`, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         undefined, | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |         plugin | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (strapi.plugins['users-permissions']) { | 
					
						
							| 
									
										
										
										
											2019-09-02 17:01:04 +08:00
										 |  |  |       policies.unshift('plugins.users-permissions.permissions'); | 
					
						
							| 
									
										
										
										
											2019-09-01 02:39:34 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     // Populate policies.
 | 
					
						
							|  |  |  |     policies.forEach(policy => | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |       policyUtils.get( | 
					
						
							|  |  |  |         policy, | 
					
						
							|  |  |  |         plugin, | 
					
						
							|  |  |  |         policiesFn, | 
					
						
							|  |  |  |         `GraphQL query "${queryName}"`, | 
					
						
							|  |  |  |         name | 
					
						
							|  |  |  |       ) | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-07 10:03:37 +02:00
										 |  |  |     return async (obj, options = {}, graphqlContext) => { | 
					
						
							|  |  |  |       const { context } = graphqlContext; | 
					
						
							| 
									
										
										
										
											2019-01-16 12:24:29 +01:00
										 |  |  |       const _options = _.cloneDeep(options); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:35:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       // Hack to be able to handle permissions for each query.
 | 
					
						
							|  |  |  |       const ctx = Object.assign(_.clone(context), { | 
					
						
							|  |  |  |         request: Object.assign(_.clone(context.request), { | 
					
						
							|  |  |  |           graphql: null, | 
					
						
							|  |  |  |         }), | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-16 22:23:17 +08: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 = this.amountLimiting(_options); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-23 01:20:13 +02:00
										 |  |  |       Object.defineProperty(ctx, 'query', { | 
					
						
							|  |  |  |         enumerable: true, | 
					
						
							|  |  |  |         configurable: true, | 
					
						
							|  |  |  |         writable: true, | 
					
						
							|  |  |  |         value: { | 
					
						
							|  |  |  |           ...this.convertToParams(_.omit(opts, 'where')), | 
					
						
							|  |  |  |           ...this.convertToQuery(opts.where), | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-10-16 22:23:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-23 01:20:13 +02:00
										 |  |  |       Object.defineProperty(ctx, 'params', { | 
					
						
							|  |  |  |         enumerable: true, | 
					
						
							|  |  |  |         configurable: true, | 
					
						
							|  |  |  |         writable: true, | 
					
						
							|  |  |  |         value: this.convertToParams(opts), | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2019-10-16 22:23:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       // Execute policies stack.
 | 
					
						
							| 
									
										
										
										
											2019-06-08 18:50:07 +02:00
										 |  |  |       const policy = await compose(policiesFn)(ctx); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Policy doesn't always return errors but they update the current context.
 | 
					
						
							| 
									
										
										
										
											2019-06-11 13:56:24 +02:00
										 |  |  |       if ( | 
					
						
							|  |  |  |         _.isError(ctx.request.graphql) || | 
					
						
							|  |  |  |         _.get(ctx.request.graphql, 'isBoom') | 
					
						
							|  |  |  |       ) { | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |         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)) { | 
					
						
							|  |  |  |         if (isController) { | 
					
						
							| 
									
										
										
										
											2019-04-18 11:33:19 +02:00
										 |  |  |           const values = await resolver.call(null, ctx, null); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if (ctx.body) { | 
					
						
							|  |  |  |             return ctx.body; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return values && values.toJSON ? values.toJSON() : values; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-07 10:03:37 +02:00
										 |  |  |         return resolver.call(null, obj, opts, graphqlContext); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Resolver can be a promise.
 | 
					
						
							|  |  |  |       return resolver; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | }; |