| 
									
										
										
										
											2018-03-27 17:15:28 +02:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * GraphQL.js service | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @description: A set of functions similar to controller's actions to avoid code duplication. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | const policyUtils = require('strapi-utils').policy; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  | const fs = require('fs'); | 
					
						
							|  |  |  | const path = require('path'); | 
					
						
							|  |  |  | const _ = require('lodash'); | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  | const pluralize = require('pluralize'); | 
					
						
							| 
									
										
										
										
											2018-04-02 17:56:17 +02:00
										 |  |  | const graphql = require('graphql'); | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | const { makeExecutableSchema } = require('graphql-tools'); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | const GraphQLJSON = require('graphql-type-json'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | module.exports = { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Receive an Object and return a string which is following the GraphQL specs. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return String | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |   formatGQL: function (fields, description = {}, model = {}, type = 'field') { | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |     const typeFields = JSON.stringify(fields, null, 2).replace(/['",]+/g, ''); | 
					
						
							|  |  |  |     const lines = typeFields.split('\n'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Try to add description for field.
 | 
					
						
							|  |  |  |     if (type === 'field') { | 
					
						
							|  |  |  |       return lines | 
					
						
							|  |  |  |         .map((line, index) => { | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |           if (['{', '}'].includes(line)) { | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |             return ``; | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const split = line.split(':'); | 
					
						
							|  |  |  |           const attribute = _.trim(split[0]); | 
					
						
							| 
									
										
										
										
											2018-04-03 12:14:34 +02:00
										 |  |  |           const info = (_.isString(description[attribute]) ? description[attribute] : _.get(description[attribute], 'description')) || _.get(model, `attributes.${attribute}.description`); | 
					
						
							|  |  |  |           const deprecated = _.get(description[attribute], 'deprecated') || _.get(model, `attributes.${attribute}.deprecated`); | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if (info) { | 
					
						
							| 
									
										
										
										
											2018-04-03 12:14:34 +02:00
										 |  |  |             line = `  """\n    ${info}\n  """\n${line}`; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (deprecated) { | 
					
						
							|  |  |  |             line = `${line} @deprecated(reason: "${deprecated}")`; | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return line; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .join('\n'); | 
					
						
							| 
									
										
										
										
											2018-04-03 12:14:34 +02:00
										 |  |  |     } else if (type === 'query') { | 
					
						
							|  |  |  |       return lines | 
					
						
							|  |  |  |         .map((line, index) => { | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |           if (['{', '}'].includes(line)) { | 
					
						
							| 
									
										
										
										
											2018-04-03 12:14:34 +02:00
										 |  |  |             return ``; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           const split = Object.keys(fields)[index - 1].split('('); | 
					
						
							|  |  |  |           const attribute = _.trim(split[0]); | 
					
						
							|  |  |  |           const info = _.get(description[attribute], 'description'); | 
					
						
							|  |  |  |           const deprecated = _.get(description[attribute], 'deprecated'); | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-03 12:14:34 +02:00
										 |  |  |           if (info) { | 
					
						
							|  |  |  |             line = `  """\n    ${info}\n  """\n${line}`; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (deprecated) { | 
					
						
							|  |  |  |             line = `${line} @deprecated(reason: "${deprecated}")`; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return line; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .join('\n'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return lines | 
					
						
							|  |  |  |         .map((line, index) => { | 
					
						
							|  |  |  |           if ([0, lines.length - 1].includes(index)) { | 
					
						
							|  |  |  |             return ``; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return line; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         .join('\n'); | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Retrieve description from variable and return a string which follow the GraphQL specs. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return String | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |   getDescription: (description, model = {}) => { | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |     const format = `"""\n`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 17:56:17 +02:00
										 |  |  |     const str = _.get(description, `_description`) || | 
					
						
							|  |  |  |       _.isString(description) ? description : undefined || | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |       _.get(model, 'info.description'); | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (str) { | 
					
						
							|  |  |  |       return `${format}${str}\n${format}`; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ``; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |   convertToParams: (params) => { | 
					
						
							|  |  |  |     return Object.keys(params).reduce((acc, current) => { | 
					
						
							|  |  |  |       return Object.assign(acc, { | 
					
						
							|  |  |  |         [`_${current}`]: params[current] | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }, {}); | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Convert Strapi type to GraphQL type. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return String | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |   convertType: (definition = {}) => { | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |     // Type.
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |     if (definition.type) { | 
					
						
							|  |  |  |       switch (definition.type) { | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |         case 'string': | 
					
						
							|  |  |  |         case 'text': | 
					
						
							|  |  |  |           return 'String'; | 
					
						
							|  |  |  |         case 'boolean': | 
					
						
							|  |  |  |           return 'Boolean'; | 
					
						
							|  |  |  |         case 'integer': | 
					
						
							|  |  |  |           return 'Int'; | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |         case 'float': | 
					
						
							|  |  |  |           return 'Float'; | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |         default: | 
					
						
							|  |  |  |           return 'String'; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |     const ref = definition.model || definition.collection; | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Association.
 | 
					
						
							|  |  |  |     if (ref && ref !== '*') { | 
					
						
							|  |  |  |       // Add bracket or not.
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |       const globalId = definition.plugin ? | 
					
						
							|  |  |  |         strapi.plugins[definition.plugin].models[ref].globalId: | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |         strapi.models[ref].globalId; | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |       const plural = !_.isEmpty(definition.collection); | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       if (plural) { | 
					
						
							|  |  |  |         return `[${globalId}]`; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return globalId; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return definition.model ? `Morph` : `[Morph]`; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Execute policies before the specified resolver. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return Promise or Error. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |   composeResolver: function (_schema, plugin, name, isSingular) { | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     const params = { | 
					
						
							|  |  |  |       model: name | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const queryOpts = plugin ? { source: plugin } : {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |     const model = plugin ? | 
					
						
							|  |  |  |       strapi.plugins[plugin].models[name]: | 
					
						
							|  |  |  |       strapi.models[name]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     // Retrieve generic service from the Content Manager plugin.
 | 
					
						
							|  |  |  |     const resolvers = strapi.plugins['content-manager'].services['contentmanager']; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |     // Extract custom resolver or type description.
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     const { resolver: handler = {} } = _schema; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |     const queryName = isSingular ? | 
					
						
							|  |  |  |       pluralize.singular(name): | 
					
						
							|  |  |  |       pluralize.plural(name); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     // Retrieve policies.
 | 
					
						
							|  |  |  |     const policies = isSingular ? | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       _.get(handler, `Query.${pluralize.singular(name)}.policies`, []): | 
					
						
							|  |  |  |       _.get(handler, `Query.${pluralize.plural(name)}.policies`, []); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 15:54:34 +02:00
										 |  |  |     // Retrieve resolverOf.
 | 
					
						
							|  |  |  |     const resolverOf = isSingular ? | 
					
						
							|  |  |  |       _.get(handler, `Query.${pluralize.singular(name)}.resolverOf`, ''): | 
					
						
							|  |  |  |       _.get(handler, `Query.${pluralize.plural(name)}.resolverOf`, ''); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02:00
										 |  |  |     const policiesFn = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |     // Boolean to define if the resolver is going to be a resolver or not.
 | 
					
						
							|  |  |  |     let isController = false; | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Retrieve resolver. It could be the custom resolver of the user
 | 
					
						
							|  |  |  |     // or the shadow CRUD resolver (aka Content-Manager).
 | 
					
						
							|  |  |  |     const resolver = (() => { | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       // Try to retrieve custom resolver.
 | 
					
						
							|  |  |  |       const resolver = isSingular ? | 
					
						
							|  |  |  |         _.get(handler, `Query.${pluralize.singular(name)}.resolver`): | 
					
						
							|  |  |  |         _.get(handler, `Query.${pluralize.plural(name)}.resolver`); | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 12:22:51 +02:00
										 |  |  |       if (_.isString(resolver)) { | 
					
						
							|  |  |  |         // Retrieve the controller's action to be executed.
 | 
					
						
							|  |  |  |         const [ name, action ] = resolver.split('.'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02:00
										 |  |  |         const controller = plugin ? | 
					
						
							|  |  |  |           _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`): | 
					
						
							|  |  |  |           _.get(strapi.controllers, `${_.toLower(name)}.${action}`); | 
					
						
							| 
									
										
										
										
											2018-04-10 12:22:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (!controller) { | 
					
						
							|  |  |  |           return new Error(`Cannot find the controller's action ${name}.${action}`); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // We're going to return a controller instead.
 | 
					
						
							|  |  |  |         isController = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02:00
										 |  |  |         // Push global policy to make sure the permissions will work as expected.
 | 
					
						
							|  |  |  |         policiesFn.push( | 
					
						
							|  |  |  |           policyUtils.globalPolicy(undefined, { | 
					
						
							|  |  |  |             handler: `${name}.${action}` | 
					
						
							|  |  |  |           }, undefined, plugin) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |         // Return the controller.
 | 
					
						
							|  |  |  |         return controller; | 
					
						
							| 
									
										
										
										
											2018-04-10 15:54:34 +02:00
										 |  |  |       } else if (resolver) { | 
					
						
							| 
									
										
										
										
											2018-04-10 12:22:51 +02:00
										 |  |  |         // Function.
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |         return resolver; | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       // We're going to return a controller instead.
 | 
					
						
							|  |  |  |       isController = true; | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 15:54:34 +02:00
										 |  |  |       const controllers = plugin ? strapi.plugins[plugin].controllers : strapi.controllers; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       // Try to find the controller that should be related to this model.
 | 
					
						
							|  |  |  |       const controller = isSingular ? | 
					
						
							| 
									
										
										
										
											2018-04-10 15:54:34 +02:00
										 |  |  |         _.get(controllers, `${name}.findOne`): | 
					
						
							|  |  |  |         _.get(controllers, `${name}.find`); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       if (!controller) { | 
					
						
							|  |  |  |         return new Error(`Cannot find the controller's action ${name}.${isSingular ? 'findOne' : 'find'}`); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02: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, plugin) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       // Make the query compatible with our controller by
 | 
					
						
							|  |  |  |       // setting in the context the parameters.
 | 
					
						
							|  |  |  |       if (isSingular) { | 
					
						
							|  |  |  |         return async (ctx, next) => { | 
					
						
							|  |  |  |           ctx.params = { | 
					
						
							|  |  |  |             ...params, | 
					
						
							| 
									
										
										
										
											2018-04-10 19:02:21 +02:00
										 |  |  |             [model.primaryKey]: ctx.params.id | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |           }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           // Return the controller.
 | 
					
						
							|  |  |  |           return controller(ctx, next); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |       // Plural.
 | 
					
						
							|  |  |  |       return async (ctx, next) => { | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |         ctx.query = Object.assign( | 
					
						
							|  |  |  |           this.convertToParams(_.omit(ctx.params, 'where')), | 
					
						
							|  |  |  |           ctx.params.where | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return controller(ctx, next); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     })(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 15:54:34 +02: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 ? | 
					
						
							|  |  |  |         _.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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  |     if (strapi.plugins['users-permissions']) { | 
					
						
							|  |  |  |       policies.push('plugins.users-permissions.permissions'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |     // Populate policies.
 | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |     policies.forEach(policy => policyUtils.get(policy, plugin, policiesFn, `GraphQL query "${queryName}"`, name)); | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |     return async (obj, options, context) => { | 
					
						
							|  |  |  |       // Hack to be able to handle permissions for each query.
 | 
					
						
							|  |  |  |       const ctx = Object.assign(context, { | 
					
						
							|  |  |  |         request: Object.assign(_.clone(context.request), { | 
					
						
							|  |  |  |           graphql: null | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2018-04-10 11:47:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       // Execute policies stack.
 | 
					
						
							|  |  |  |       const policy = await strapi.koaMiddlewares.compose(policiesFn)(ctx); | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       // 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; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       // Something went wrong in the policy.
 | 
					
						
							|  |  |  |       if (policy) { | 
					
						
							|  |  |  |         return policy; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       // Resolver can be a function. Be also a native resolver or a controller's action.
 | 
					
						
							|  |  |  |       if (_.isFunction(resolver)) { | 
					
						
							| 
									
										
										
										
											2018-04-10 19:02:21 +02:00
										 |  |  |         context.query = this.convertToParams(options); | 
					
						
							|  |  |  |         context.params = options; | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 19:02:21 +02:00
										 |  |  |         if (isController) { | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |           const values = await resolver.call(null, context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           if (ctx.body) { | 
					
						
							|  |  |  |             return ctx.body; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |           return values && values.toJSON ? values.toJSON() : values; | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |         return resolver.call(null, obj, options, context) | 
					
						
							| 
									
										
										
										
											2018-04-10 12:56:13 +02:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       // Resolver can be a promise.
 | 
					
						
							|  |  |  |       return resolver; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Construct the GraphQL query & definition and apply the right resolvers. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return Object | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  |   shadowCRUD: function (models, plugin) { | 
					
						
							| 
									
										
										
										
											2018-04-02 17:36:11 +02:00
										 |  |  |     // Retrieve generic service from the Content Manager plugin.
 | 
					
						
							|  |  |  |     const resolvers = strapi.plugins['content-manager'].services['contentmanager']; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |     const initialState = { definition: ``, query: {}, resolver: { Query : {} } }; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |     if (_.isEmpty(models)) { | 
					
						
							|  |  |  |       return initialState; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  |     return models.reduce((acc, name) => { | 
					
						
							|  |  |  |       const model = plugin ? | 
					
						
							|  |  |  |         strapi.plugins[plugin].models[name]: | 
					
						
							|  |  |  |         strapi.models[name]; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |       // Setup initial state with default attribute that should be displayed
 | 
					
						
							|  |  |  |       // but these attributes are not properly defined in the models.
 | 
					
						
							|  |  |  |       const initialState = { | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  |         [model.primaryKey]: 'String' | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-17 18:16:31 +02:00
										 |  |  |       const globalId = model.globalId; | 
					
						
							|  |  |  |       const _schema = _.cloneDeep(_.get(strapi.plugins, `graphql.config._schema.graphql`, {})); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!acc.resolver[globalId]) { | 
					
						
							|  |  |  |         acc.resolver[globalId] = {}; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |       // Add timestamps attributes.
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  |       if (_.get(model, 'options.timestamps') === true) { | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |         Object.assign(initialState, { | 
					
						
							|  |  |  |           created_at: 'String', | 
					
						
							|  |  |  |           updated_at: 'String' | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-17 18:16:31 +02:00
										 |  |  |         Object.assign(acc.resolver[globalId], { | 
					
						
							|  |  |  |           created_at: (obj, options, context) => { | 
					
						
							|  |  |  |             return obj.createdAt || obj.created_at; | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           updated_at: (obj, options, context) => { | 
					
						
							|  |  |  |             return obj.updatedAt || obj.updated_at; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Retrieve user customisation.
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       const { type = {}, resolver = {} } = _schema; | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |       // Convert our layer Model to the GraphQL DL.
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  |       const attributes = Object.keys(model.attributes) | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |         .reduce((acc, attribute) => { | 
					
						
							|  |  |  |           // Convert our type to the GraphQL type.
 | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |           acc[attribute] = this.convertType(model.attributes[attribute]); | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           return acc; | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |         }, initialState); | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 17:36:11 +02:00
										 |  |  |       // Add parameters to optimize association query.
 | 
					
						
							|  |  |  |       (model.associations || []) | 
					
						
							|  |  |  |         .filter(association => association.type === 'collection') | 
					
						
							|  |  |  |         .forEach(association => { | 
					
						
							|  |  |  |           attributes[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] = attributes[association.alias]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           delete attributes[association.alias]; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |       acc.definition += `${this.getDescription(type[globalId], model)}type ${globalId} {${this.formatGQL(attributes, type[globalId], model)}}\n\n`; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-03 11:30:39 +02:00
										 |  |  |       // Add definition to the schema but this type won't be "queriable".
 | 
					
						
							| 
									
										
										
										
											2018-04-03 18:14:56 +02:00
										 |  |  |       if (type[model.globalId] === false || _.get(type, `${model.globalId}.enabled`) === false) { | 
					
						
							| 
									
										
										
										
											2018-04-03 11:30:39 +02:00
										 |  |  |         return acc; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       // Build resolvers.
 | 
					
						
							|  |  |  |       const queries = { | 
					
						
							|  |  |  |         singular: _.get(resolver, `Query.${pluralize.singular(name)}`) !== false ? this.composeResolver( | 
					
						
							|  |  |  |           _schema, | 
					
						
							|  |  |  |           plugin, | 
					
						
							|  |  |  |           name, | 
					
						
							|  |  |  |           true | 
					
						
							|  |  |  |         ) : null, | 
					
						
							|  |  |  |         plural: _.get(resolver, `Query.${pluralize.plural(name)}`) !== false ? this.composeResolver( | 
					
						
							|  |  |  |           _schema, | 
					
						
							|  |  |  |           plugin, | 
					
						
							|  |  |  |           name, | 
					
						
							|  |  |  |           false | 
					
						
							|  |  |  |         ) : null | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |       // TODO:
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |       // - Handle mutations.
 | 
					
						
							| 
									
										
										
										
											2018-04-10 18:54:01 +02:00
										 |  |  |       Object.keys(queries).forEach(type => { | 
					
						
							|  |  |  |         // The query cannot be built.
 | 
					
						
							|  |  |  |         if (_.isError(queries[type])) { | 
					
						
							|  |  |  |           console.error(queries[type]); | 
					
						
							|  |  |  |           strapi.stop(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Only create query if the function is available.
 | 
					
						
							|  |  |  |         if (_.isFunction(queries[type])) { | 
					
						
							|  |  |  |           if (type === 'singular') { | 
					
						
							|  |  |  |             Object.assign(acc.query, { | 
					
						
							|  |  |  |               [`${pluralize.singular(name)}(id: String!)`]: model.globalId | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             Object.assign(acc.query, { | 
					
						
							|  |  |  |               [`${pluralize.plural(name)}(sort: String, limit: Int, start: Int, where: JSON)`]: `[${model.globalId}]` | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           _.merge(acc.resolver.Query, { | 
					
						
							|  |  |  |             [type === 'singular' ? pluralize.singular(name) : pluralize.plural(name)]: queries[type] | 
					
						
							|  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Build associations queries.
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |       (model.associations || []).forEach(association => { | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  |         switch (association.nature) { | 
					
						
							|  |  |  |           case 'manyMorphToOne': | 
					
						
							|  |  |  |           case 'manyMorphToMany': | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |           case 'manyToManyMorph': | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  |             return _.merge(acc.resolver[globalId], { | 
					
						
							|  |  |  |               [association.alias]: async (obj, options, context) => { | 
					
						
							|  |  |  |                 const [ withRelated, withoutRelated ] = await Promise.all([ | 
					
						
							|  |  |  |                   resolvers.fetch({ | 
					
						
							|  |  |  |                     id: obj[model.primaryKey], | 
					
						
							|  |  |  |                     model: name | 
					
						
							|  |  |  |                   }, plugin, [association.alias], false), | 
					
						
							|  |  |  |                   resolvers.fetch({ | 
					
						
							|  |  |  |                     id: obj[model.primaryKey], | 
					
						
							|  |  |  |                     model: name | 
					
						
							|  |  |  |                   }, plugin, []) | 
					
						
							|  |  |  |                 ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |                 const entry = withRelated && withRelated.toJSON ? withRelated.toJSON() : withRelated; | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 entry[association.alias].map((entry, index) => { | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |                   const type = _.get(withoutRelated, `${association.alias}.${index}.kind`) || | 
					
						
							|  |  |  |                   _.upperFirst(_.camelCase(_.get(withoutRelated, `${association.alias}.${index}.${association.alias}_type`))) || | 
					
						
							|  |  |  |                   _.upperFirst(_.camelCase(association[association.type])); | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                   entry._type = type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                   return entry; | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return entry[association.alias]; | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           default: | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |         _.merge(acc.resolver[globalId], { | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  |           [association.alias]: async (obj, options, context) => { | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |             // Construct parameters object to retrieve the correct related entries.
 | 
					
						
							|  |  |  |             const params = { | 
					
						
							|  |  |  |               model: association.model || association.collection, | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const queryOpts = { | 
					
						
							|  |  |  |               source: association.plugin | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (association.type === 'model') { | 
					
						
							|  |  |  |               params.id = obj[association.alias]; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               // Get attribute.
 | 
					
						
							|  |  |  |               const attr = association.plugin ? | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  |                 strapi.plugins[association.plugin].models[params.model].attributes[association.via]: | 
					
						
							|  |  |  |                 strapi.models[params.model].attributes[association.via]; | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |               // Get refering model.
 | 
					
						
							|  |  |  |               const ref = attr.plugin ? | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  |                 strapi.plugins[attr.plugin].models[params.model]: | 
					
						
							|  |  |  |                 strapi.models[params.model]; | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 17:36:11 +02:00
										 |  |  |               // Apply optional arguments to make more precise nested request.
 | 
					
						
							|  |  |  |               const convertedParams = strapi.utils.models.convertParams(name, this.convertToParams(options)); | 
					
						
							|  |  |  |               const where = strapi.utils.models.convertParams(name, options.where || {}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  |               // Limit, order, etc.
 | 
					
						
							| 
									
										
										
										
											2018-04-02 17:36:11 +02:00
										 |  |  |               Object.assign(queryOpts, convertedParams); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  |               // Skip.
 | 
					
						
							|  |  |  |               queryOpts.skip = convertedParams.start; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |               switch (association.nature) { | 
					
						
							|  |  |  |                 case 'manyToMany': | 
					
						
							|  |  |  |                   const arrayOfIds = obj[association.alias].map(related => { | 
					
						
							|  |  |  |                     return related[ref.primaryKey] || related; | 
					
						
							|  |  |  |                   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                   // Where.
 | 
					
						
							|  |  |  |                   queryOpts.query = strapi.utils.models.convertParams(name, { | 
					
						
							|  |  |  |                     // Construct the "where" query to only retrieve entries which are
 | 
					
						
							|  |  |  |                     // related to this entry.
 | 
					
						
							|  |  |  |                     [ref.primaryKey]: arrayOfIds, | 
					
						
							|  |  |  |                     ...where.where | 
					
						
							|  |  |  |                   }).where; | 
					
						
							|  |  |  |                   break; | 
					
						
							|  |  |  |                 default: | 
					
						
							|  |  |  |                   // Where.
 | 
					
						
							|  |  |  |                   queryOpts.query = strapi.utils.models.convertParams(name, { | 
					
						
							|  |  |  |                     // Construct the "where" query to only retrieve entries which are
 | 
					
						
							|  |  |  |                     // related to this entry.
 | 
					
						
							|  |  |  |                     [association.via]: obj[ref.primaryKey], | 
					
						
							|  |  |  |                     ...where.where | 
					
						
							|  |  |  |                   }).where; | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  |             const value = await (association.model ? | 
					
						
							| 
									
										
										
										
											2018-04-03 11:30:39 +02:00
										 |  |  |               resolvers.fetch(params, association.plugin, []): | 
					
						
							|  |  |  |               resolvers.fetchAll(params, { ...queryOpts, populate: [] })); | 
					
						
							| 
									
										
										
										
											2018-04-02 19:24:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-17 17:29:24 +02:00
										 |  |  |             return value && value.toJSON ? value.toJSON() : value; | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |       return acc; | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |     }, initialState); | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Generate GraphQL schema. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return Schema | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |   generateSchema: function () { | 
					
						
							|  |  |  |     // Generate type definition and query/mutation for models.
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  |     const shadowCRUD = strapi.plugins.graphql.config.shadowCRUD !== false ? (() => { | 
					
						
							|  |  |  |       // Exclude core models.
 | 
					
						
							|  |  |  |       const models = Object.keys(strapi.models).filter(model => model !== 'core_store'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Reproduce the same pattern for each plugin.
 | 
					
						
							|  |  |  |       return Object.keys(strapi.plugins).reduce((acc, plugin) => { | 
					
						
							|  |  |  |         const { definition, query, resolver } = this.shadowCRUD(Object.keys(strapi.plugins[plugin].models), plugin); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // We cannot put this in the merge because it's a string.
 | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |         acc.definition += definition || ``; | 
					
						
							| 
									
										
										
										
											2018-03-30 17:33:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return _.merge(acc, { | 
					
						
							|  |  |  |           query, | 
					
						
							|  |  |  |           resolver | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }, this.shadowCRUD(models)); | 
					
						
							|  |  |  |     })() : {}; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     // Extract custom definition, query or resolver.
 | 
					
						
							| 
									
										
										
										
											2018-04-03 12:14:34 +02:00
										 |  |  |     const { definition, query, resolver = {} } = strapi.plugins.graphql.config._schema.graphql; | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |     // Polymorphic.
 | 
					
						
							|  |  |  |     const { polymorphicDef, polymorphicResolver } = this.addPolymorphicUnionType(definition, shadowCRUD.definition); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |     // Build resolvers.
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |     const resolvers = _.omitBy(_.merge(shadowCRUD.resolver, resolver, polymorphicResolver), _.isEmpty) || {}; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |     // Transform object to only contain function.
 | 
					
						
							|  |  |  |     Object.keys(resolvers).reduce((acc, type) => { | 
					
						
							|  |  |  |       return Object.keys(acc[type]).reduce((acc, resolver) => { | 
					
						
							| 
									
										
										
										
											2018-04-03 11:30:39 +02:00
										 |  |  |         // Disabled this query.
 | 
					
						
							|  |  |  |         if (acc[type][resolver] === false) { | 
					
						
							|  |  |  |           delete acc[type][resolver]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return acc; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |         acc[type][resolver] = _.isFunction(acc[type][resolver]) ? | 
					
						
							|  |  |  |           acc[type][resolver]: | 
					
						
							|  |  |  |           acc[type][resolver].resolver; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return acc; | 
					
						
							|  |  |  |       }, acc); | 
					
						
							|  |  |  |     }, resolvers); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |     // Return empty schema when there is no model.
 | 
					
						
							| 
									
										
										
										
											2018-03-31 18:55:08 +02:00
										 |  |  |     if (_.isEmpty(shadowCRUD.definition) && _.isEmpty(definition)) { | 
					
						
							| 
									
										
										
										
											2018-03-29 14:03:09 +02:00
										 |  |  |       return {}; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |     // Concatenate.
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |     const typeDefs = `
 | 
					
						
							|  |  |  |       ${definition} | 
					
						
							|  |  |  |       ${shadowCRUD.definition} | 
					
						
							|  |  |  |       type Query {${this.formatGQL(shadowCRUD.query, resolver.Query, null, 'query')}${query}} | 
					
						
							|  |  |  |       ${this.addCustomScalar(resolvers)} | 
					
						
							|  |  |  |       ${polymorphicDef} | 
					
						
							|  |  |  |     `;
 | 
					
						
							| 
									
										
										
										
											2018-04-03 11:30:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |     // Build schema.
 | 
					
						
							|  |  |  |     const schema = makeExecutableSchema({ | 
					
						
							|  |  |  |       typeDefs, | 
					
						
							|  |  |  |       resolvers, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 17:56:17 +02:00
										 |  |  |     // Write schema.
 | 
					
						
							|  |  |  |     this.writeGenerateSchema(graphql.printSchema(schema)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |     return schema; | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:31:27 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Add custom scalar type such as JSON. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return void | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addCustomScalar: (resolvers) => { | 
					
						
							|  |  |  |     Object.assign(resolvers, { | 
					
						
							|  |  |  |       JSON: GraphQLJSON | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return `scalar JSON`; | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Add Union Type that contains the types defined by the user. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return string | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   addPolymorphicUnionType: (customDefs, defs) => { | 
					
						
							|  |  |  |     const types = graphql.parse(customDefs + defs).definitions | 
					
						
							|  |  |  |       .filter(def => def.name.value !== 'Query') | 
					
						
							|  |  |  |       .map(def => def.name.value); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       polymorphicDef: `union Morph = ${types.join(' | ')}`, | 
					
						
							|  |  |  |       polymorphicResolver: { | 
					
						
							|  |  |  |         Morph: { | 
					
						
							| 
									
										
										
										
											2018-04-11 12:53:07 +02:00
										 |  |  |           __resolveType(obj, context, info) { | 
					
						
							| 
									
										
										
										
											2018-04-05 15:20:24 +02:00
										 |  |  |             return obj.kind || obj._type; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 17:05:24 +02:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Save into a file the readable GraphQL schema. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @return void | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |   writeGenerateSchema(schema) { | 
					
						
							| 
									
										
										
										
											2018-03-28 20:13:09 +02:00
										 |  |  |     // Disable auto-reload.
 | 
					
						
							|  |  |  |     strapi.reload.isWatching = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-28 18:40:59 +02:00
										 |  |  |     const generatedFolder = path.resolve(strapi.config.appPath, 'plugins', 'graphql', 'config', 'generated'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Create folder if necessary.
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       fs.accessSync(generatedFolder, fs.constants.R_OK | fs.constants.W_OK); | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							|  |  |  |       if (err && err.code === 'ENOENT') { | 
					
						
							|  |  |  |         fs.mkdirSync(generatedFolder); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         console.error(err); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fs.writeFileSync(path.join(generatedFolder, 'schema.graphql'), schema); | 
					
						
							| 
									
										
										
										
											2018-03-28 20:13:09 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     strapi.reload.isWatching = true; | 
					
						
							| 
									
										
										
										
											2018-03-27 19:02:04 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }; |