| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * GraphQL.js service | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @description: A set of functions similar to controller's actions to avoid code duplication. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const _ = require('lodash'); | 
					
						
							| 
									
										
										
										
											2019-11-28 16:10:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | const DynamicZoneScalar = require('../types/dynamiczoneScalar'); | 
					
						
							| 
									
										
										
										
											2019-11-04 11:29:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | const Aggregator = require('./Aggregator'); | 
					
						
							|  |  |  | const Query = require('./Query.js'); | 
					
						
							|  |  |  | const Mutation = require('./Mutation.js'); | 
					
						
							|  |  |  | const Types = require('./Types.js'); | 
					
						
							|  |  |  | const Schema = require('./Schema.js'); | 
					
						
							| 
									
										
										
										
											2019-11-04 11:29:19 +01:00
										 |  |  | const { toSingular, toPlural } = require('./naming'); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | const convertAttributes = (attributes, globalId) => { | 
					
						
							|  |  |  |   return Object.keys(attributes) | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |     .filter(attribute => attributes[attribute].private !== true) | 
					
						
							|  |  |  |     .reduce((acc, attribute) => { | 
					
						
							|  |  |  |       // Convert our type to the GraphQL type.
 | 
					
						
							|  |  |  |       acc[attribute] = Types.convertType({ | 
					
						
							|  |  |  |         definition: attributes[attribute], | 
					
						
							|  |  |  |         modelName: globalId, | 
					
						
							|  |  |  |         attributeName: attribute, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       return acc; | 
					
						
							|  |  |  |     }, {}); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | const generateEnumDefinitions = (attributes, globalId) => { | 
					
						
							|  |  |  |   return Object.keys(attributes) | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |     .filter(attribute => attributes[attribute].type === 'enumeration') | 
					
						
							|  |  |  |     .map(attribute => { | 
					
						
							|  |  |  |       const definition = attributes[attribute]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |       const name = Types.convertEnumType(definition, globalId, attribute); | 
					
						
							|  |  |  |       const values = definition.enum.map(v => `\t${v}`).join('\n'); | 
					
						
							|  |  |  |       return `enum ${name} {\n${values}\n}\n`; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |     }) | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |     .join(''); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  | const generateDynamicZoneDefinitions = (attributes, globalId, schema) => { | 
					
						
							|  |  |  |   Object.keys(attributes) | 
					
						
							|  |  |  |     .filter(attribute => attributes[attribute].type === 'dynamiczone') | 
					
						
							|  |  |  |     .forEach(attribute => { | 
					
						
							|  |  |  |       const { components } = attributes[attribute]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const typeName = `${globalId}${_.upperFirst( | 
					
						
							|  |  |  |         _.camelCase(attribute) | 
					
						
							|  |  |  |       )}DynamicZone`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (components.length === 0) { | 
					
						
							|  |  |  |         // Create dummy type because graphql doesn't support empty ones
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 16:17:46 +01:00
										 |  |  |         schema.definition += `type ${typeName} { _:Boolean}`; | 
					
						
							|  |  |  |         schema.definition += `\nscalar EmptyQuery\n`; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         const componentsTypeNames = components.map(componentUID => { | 
					
						
							|  |  |  |           const compo = strapi.components[componentUID]; | 
					
						
							|  |  |  |           if (!compo) { | 
					
						
							|  |  |  |             throw new Error( | 
					
						
							|  |  |  |               `Trying to creating dynamiczone type with unkown component ${componentUID}` | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return compo.globalId; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const unionType = `union ${typeName} = ${componentsTypeNames.join( | 
					
						
							|  |  |  |           ' | ' | 
					
						
							|  |  |  |         )}`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         schema.definition += `\n${unionType}\n`; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       const inputTypeName = `${typeName}Input`; | 
					
						
							| 
									
										
										
										
											2019-11-28 16:17:46 +01:00
										 |  |  |       schema.definition += `\nscalar ${inputTypeName}\n`; | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       schema.resolvers[typeName] = { | 
					
						
							|  |  |  |         __resolveType(obj) { | 
					
						
							|  |  |  |           return strapi.components[obj.__component].globalId; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 16:10:19 +01:00
										 |  |  |       schema.resolvers[inputTypeName] = new DynamicZoneScalar({ | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |         name: inputTypeName, | 
					
						
							| 
									
										
										
										
											2019-11-28 16:10:19 +01:00
										 |  |  |         attribute, | 
					
						
							|  |  |  |         globalId, | 
					
						
							|  |  |  |         components, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-01 17:45:16 +02:00
										 |  |  | const mutateAssocAttributes = (associations = [], attributes) => { | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |   associations | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |     .filter(association => association.type === 'collection') | 
					
						
							|  |  |  |     .forEach(association => { | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |       attributes[ | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |         `${association.alias}(sort: String, limit: Int, start: Int, where: JSON)` | 
					
						
							|  |  |  |       ] = attributes[association.alias]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |       delete attributes[association.alias]; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  | const buildAssocResolvers = model => { | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |   const contentManager = | 
					
						
							|  |  |  |     strapi.plugins['content-manager'].services['contentmanager']; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |   const { primaryKey, associations = [] } = model; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-07 09:19:35 +01:00
										 |  |  |   return associations | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |     .filter(association => model.attributes[association.alias].private !== true) | 
					
						
							|  |  |  |     .reduce((resolver, association) => { | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |       const target = association.model || association.collection; | 
					
						
							|  |  |  |       const targetModel = strapi.getModel(target, association.plugin); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |       switch (association.nature) { | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |         case 'oneToManyMorph': | 
					
						
							|  |  |  |         case 'manyMorphToOne': | 
					
						
							|  |  |  |         case 'manyMorphToMany': | 
					
						
							|  |  |  |         case 'manyToManyMorph': { | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |           resolver[association.alias] = async obj => { | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |             if (obj[association.alias]) { | 
					
						
							|  |  |  |               return obj[association.alias]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |             const entry = await contentManager.fetch( | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |               { | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |                 id: obj[primaryKey], | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                 model: model.uid, | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |               }, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |               [association.alias] | 
					
						
							|  |  |  |             ); | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |             return entry[association.alias]; | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |           }; | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         default: { | 
					
						
							|  |  |  |           resolver[association.alias] = async (obj, options) => { | 
					
						
							|  |  |  |             // Construct parameters object to retrieve the correct related entries.
 | 
					
						
							|  |  |  |             const params = { | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |               model: targetModel.uid, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |             }; | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |             let queryOpts = {}; | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (association.type === 'model') { | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |               params[targetModel.primaryKey] = _.get( | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |                 obj, | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                 [association.alias, targetModel.primaryKey], | 
					
						
							| 
									
										
										
										
											2019-10-01 17:56:52 +02:00
										 |  |  |                 obj[association.alias] | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |               ); | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |               const queryParams = Query.amountLimiting(options); | 
					
						
							|  |  |  |               queryOpts = { | 
					
						
							|  |  |  |                 ...queryOpts, | 
					
						
							|  |  |  |                 ...Query.convertToParams(_.omit(queryParams, 'where')), // Convert filters (sort, limit and start/skip)
 | 
					
						
							|  |  |  |                 ...Query.convertToQuery(queryParams.where), | 
					
						
							|  |  |  |               }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               if ( | 
					
						
							|  |  |  |                 ((association.nature === 'manyToMany' && | 
					
						
							|  |  |  |                   association.dominant) || | 
					
						
							|  |  |  |                   association.nature === 'manyWay') && | 
					
						
							|  |  |  |                 _.has(obj, association.alias) // if populated
 | 
					
						
							|  |  |  |               ) { | 
					
						
							|  |  |  |                 _.set( | 
					
						
							|  |  |  |                   queryOpts, | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                   ['query', targetModel.primaryKey], | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |                   obj[association.alias] | 
					
						
							|  |  |  |                     ? obj[association.alias] | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                         .map(val => val[targetModel.primaryKey] || val) | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |                         .sort() | 
					
						
							|  |  |  |                     : [] | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |               } else { | 
					
						
							|  |  |  |                 _.set( | 
					
						
							|  |  |  |                   queryOpts, | 
					
						
							|  |  |  |                   ['query', association.via], | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                   obj[targetModel.primaryKey] | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |                 ); | 
					
						
							|  |  |  |               } | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |             return association.model | 
					
						
							|  |  |  |               ? strapi.plugins.graphql.services.loaders.loaders[ | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                   targetModel.uid | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |                 ].load({ | 
					
						
							|  |  |  |                   params, | 
					
						
							|  |  |  |                   options: queryOpts, | 
					
						
							|  |  |  |                   single: true, | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |               : strapi.plugins.graphql.services.loaders.loaders[ | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |                   targetModel.uid | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |                 ].load({ | 
					
						
							|  |  |  |                   options: queryOpts, | 
					
						
							|  |  |  |                   association, | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |           break; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |       return resolver; | 
					
						
							|  |  |  |     }, {}); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  | const buildModel = (model, { schema, isComponent = false } = {}) => { | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |   const { globalId, primaryKey } = model; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |   schema.resolvers[globalId] = { | 
					
						
							|  |  |  |     id: obj => obj[primaryKey], | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |     ...buildAssocResolvers(model), | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |   const initialState = { | 
					
						
							|  |  |  |     id: 'ID!', | 
					
						
							|  |  |  |     [primaryKey]: 'ID!', | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (_.isArray(_.get(model, 'options.timestamps'))) { | 
					
						
							|  |  |  |     const [createdAtKey, updatedAtKey] = model.options.timestamps; | 
					
						
							|  |  |  |     initialState[createdAtKey] = 'DateTime!'; | 
					
						
							|  |  |  |     initialState[updatedAtKey] = 'DateTime!'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const attributes = convertAttributes(model.attributes, globalId); | 
					
						
							| 
									
										
										
										
											2019-10-01 17:45:16 +02:00
										 |  |  |   mutateAssocAttributes(model.associations, attributes); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |   _.merge(attributes, initialState); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |   schema.definition += generateEnumDefinitions(model.attributes, globalId); | 
					
						
							|  |  |  |   generateDynamicZoneDefinitions(model.attributes, globalId, schema); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const description = Schema.getDescription({}, model); | 
					
						
							|  |  |  |   const fields = Schema.formatGQL(attributes, {}, model); | 
					
						
							|  |  |  |   const typeDef = `${description}type ${globalId} {${fields}}\n`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |   schema.definition += typeDef; | 
					
						
							|  |  |  |   schema.definition += Types.generateInputModel(model, globalId, { | 
					
						
							| 
									
										
										
										
											2019-10-22 18:01:03 +02:00
										 |  |  |     allowIds: isComponent, | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |   }); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Construct the GraphQL query & definition and apply the right resolvers. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @return Object | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const buildShadowCRUD = (models, plugin) => { | 
					
						
							|  |  |  |   const initialState = { | 
					
						
							|  |  |  |     definition: '', | 
					
						
							|  |  |  |     query: {}, | 
					
						
							|  |  |  |     mutation: {}, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |     resolvers: { Query: {}, Mutation: {} }, | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |   if (_.isEmpty(models)) { | 
					
						
							|  |  |  |     return initialState; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |   return Object.keys(models).reduce((acc, name) => { | 
					
						
							|  |  |  |     const model = models[name]; | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 13:13:07 +02:00
										 |  |  |     const { globalId, primaryKey } = model; | 
					
						
							| 
									
										
										
										
											2019-11-04 11:29:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     // Setup initial state with default attribute that should be displayed
 | 
					
						
							|  |  |  |     // but these attributes are not properly defined in the models.
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     const initialState = { | 
					
						
							| 
									
										
										
										
											2019-07-17 13:13:07 +02:00
										 |  |  |       [primaryKey]: 'ID!', | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 13:13:07 +02:00
										 |  |  |     // always add an id field to make the api database agnostic
 | 
					
						
							|  |  |  |     if (primaryKey !== 'id') { | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |       initialState['id'] = 'ID!'; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |     acc.resolvers[globalId] = { | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |       // define the default id resolver
 | 
					
						
							|  |  |  |       id(parent) { | 
					
						
							|  |  |  |         return parent[model.primaryKey]; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     // Add timestamps attributes.
 | 
					
						
							|  |  |  |     if (_.isArray(_.get(model, 'options.timestamps'))) { | 
					
						
							| 
									
										
										
										
											2019-07-17 13:13:07 +02:00
										 |  |  |       const [createdAtKey, updatedAtKey] = model.options.timestamps; | 
					
						
							|  |  |  |       initialState[createdAtKey] = 'DateTime!'; | 
					
						
							|  |  |  |       initialState[updatedAtKey] = 'DateTime!'; | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 13:13:07 +02:00
										 |  |  |     const _schema = _.cloneDeep( | 
					
						
							|  |  |  |       _.get(strapi.plugins, 'graphql.config._schema.graphql', {}) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     const { type = {}, resolver = {} } = _schema; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Convert our layer Model to the GraphQL DL.
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |     const attributes = convertAttributes(model.attributes, globalId); | 
					
						
							| 
									
										
										
										
											2019-10-01 17:45:16 +02:00
										 |  |  |     mutateAssocAttributes(model.associations, attributes); | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |     _.merge(attributes, initialState); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |     acc.definition += generateEnumDefinitions(model.attributes, globalId); | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |     generateDynamicZoneDefinitions(model.attributes, globalId, acc); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 10:55:13 +02:00
										 |  |  |     const description = Schema.getDescription(type[globalId], model); | 
					
						
							|  |  |  |     const fields = Schema.formatGQL(attributes, type[globalId], model); | 
					
						
							|  |  |  |     const typeDef = `${description}type ${globalId} {${fields}}\n`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     acc.definition += typeDef; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     // Add definition to the schema but this type won't be "queriable" or "mutable".
 | 
					
						
							| 
									
										
										
										
											2019-07-09 11:24:11 +02:00
										 |  |  |     if ( | 
					
						
							|  |  |  |       type[model.globalId] === false || | 
					
						
							|  |  |  |       _.get(type, `${model.globalId}.enabled`) === false | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |       return acc; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-04 11:29:19 +01:00
										 |  |  |     const singularName = toSingular(name); | 
					
						
							|  |  |  |     const pluralName = toPlural(name); | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     // Build resolvers.
 | 
					
						
							|  |  |  |     const queries = { | 
					
						
							|  |  |  |       singular: | 
					
						
							|  |  |  |         _.get(resolver, `Query.${singularName}`) !== false | 
					
						
							| 
									
										
										
										
											2019-09-11 21:55:26 +08:00
										 |  |  |           ? Query.composeQueryResolver({ | 
					
						
							|  |  |  |               _schema, | 
					
						
							|  |  |  |               plugin, | 
					
						
							|  |  |  |               name, | 
					
						
							|  |  |  |               isSingular: true, | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           : null, | 
					
						
							|  |  |  |       plural: | 
					
						
							|  |  |  |         _.get(resolver, `Query.${pluralName}`) !== false | 
					
						
							| 
									
										
										
										
											2019-09-11 21:55:26 +08:00
										 |  |  |           ? Query.composeQueryResolver({ | 
					
						
							|  |  |  |               _schema, | 
					
						
							|  |  |  |               plugin, | 
					
						
							|  |  |  |               name, | 
					
						
							|  |  |  |               isSingular: false, | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           : null, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // check if errors
 | 
					
						
							|  |  |  |     Object.keys(queries).forEach(type => { | 
					
						
							|  |  |  |       // The query cannot be built.
 | 
					
						
							|  |  |  |       if (_.isError(queries[type])) { | 
					
						
							|  |  |  |         strapi.log.error(queries[type]); | 
					
						
							|  |  |  |         strapi.stop(); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (_.isFunction(queries.singular)) { | 
					
						
							|  |  |  |       _.merge(acc, { | 
					
						
							|  |  |  |         query: { | 
					
						
							|  |  |  |           [`${singularName}(id: ID!)`]: model.globalId, | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |         resolvers: { | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           Query: { | 
					
						
							|  |  |  |             [singularName]: queries.singular, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     if (_.isFunction(queries.plural)) { | 
					
						
							|  |  |  |       _.merge(acc, { | 
					
						
							|  |  |  |         query: { | 
					
						
							| 
									
										
										
										
											2019-07-09 11:24:11 +02:00
										 |  |  |           [`${pluralName}(sort: String, limit: Int, start: Int, where: JSON)`]: `[${model.globalId}]`, | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |         resolvers: { | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           Query: { | 
					
						
							|  |  |  |             [pluralName]: queries.plural, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // TODO:
 | 
					
						
							|  |  |  |     // - Implement batch methods (need to update the content-manager as well).
 | 
					
						
							|  |  |  |     // - Implement nested transactional methods (create/update).
 | 
					
						
							| 
									
										
										
										
											2019-11-04 11:29:19 +01:00
										 |  |  |     const capitalizedName = _.upperFirst(singularName); | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     const mutations = { | 
					
						
							|  |  |  |       create: | 
					
						
							|  |  |  |         _.get(resolver, `Mutation.create${capitalizedName}`) !== false | 
					
						
							| 
									
										
										
										
											2019-09-11 21:55:26 +08:00
										 |  |  |           ? Mutation.composeMutationResolver({ | 
					
						
							|  |  |  |               _schema, | 
					
						
							|  |  |  |               plugin, | 
					
						
							|  |  |  |               name, | 
					
						
							|  |  |  |               action: 'create', | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           : null, | 
					
						
							|  |  |  |       update: | 
					
						
							|  |  |  |         _.get(resolver, `Mutation.update${capitalizedName}`) !== false | 
					
						
							| 
									
										
										
										
											2019-09-11 21:55:26 +08:00
										 |  |  |           ? Mutation.composeMutationResolver({ | 
					
						
							|  |  |  |               _schema, | 
					
						
							|  |  |  |               plugin, | 
					
						
							|  |  |  |               name, | 
					
						
							|  |  |  |               action: 'update', | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           : null, | 
					
						
							|  |  |  |       delete: | 
					
						
							|  |  |  |         _.get(resolver, `Mutation.delete${capitalizedName}`) !== false | 
					
						
							| 
									
										
										
										
											2019-09-11 21:55:26 +08:00
										 |  |  |           ? Mutation.composeMutationResolver({ | 
					
						
							|  |  |  |               _schema, | 
					
						
							|  |  |  |               plugin, | 
					
						
							|  |  |  |               name, | 
					
						
							|  |  |  |               action: 'delete', | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           : null, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Add model Input definition.
 | 
					
						
							|  |  |  |     acc.definition += Types.generateInputModel(model, name); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Object.keys(mutations).forEach(type => { | 
					
						
							|  |  |  |       if (_.isFunction(mutations[type])) { | 
					
						
							|  |  |  |         let mutationDefinition; | 
					
						
							|  |  |  |         let mutationName = `${type}${capitalizedName}`; | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |         // Generate the Input for this specific action.
 | 
					
						
							| 
									
										
										
										
											2019-07-09 11:24:11 +02:00
										 |  |  |         acc.definition += Types.generateInputPayloadArguments( | 
					
						
							|  |  |  |           model, | 
					
						
							|  |  |  |           name, | 
					
						
							|  |  |  |           type | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         switch (type) { | 
					
						
							|  |  |  |           case 'create': | 
					
						
							|  |  |  |             mutationDefinition = { | 
					
						
							|  |  |  |               [`${mutationName}(input: ${mutationName}Input)`]: `${mutationName}Payload`, | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case 'update': | 
					
						
							|  |  |  |             mutationDefinition = { | 
					
						
							|  |  |  |               [`${mutationName}(input: ${mutationName}Input)`]: `${mutationName}Payload`, | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           case 'delete': | 
					
						
							|  |  |  |             mutationDefinition = { | 
					
						
							|  |  |  |               [`${mutationName}(input: ${mutationName}Input)`]: `${mutationName}Payload`, | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |           default: | 
					
						
							|  |  |  |           // Nothing.
 | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |         // Assign mutation definition to global definition.
 | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |         _.merge(acc, { | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |           mutation: mutationDefinition, | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |           resolvers: { | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |             Mutation: { | 
					
						
							|  |  |  |               [`${mutationName}`]: mutations[type], | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |     // TODO: Add support for Graphql Aggregation in Bookshelf ORM
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     if (model.orm === 'mongoose') { | 
					
						
							|  |  |  |       // Generation the aggregation for the given model
 | 
					
						
							|  |  |  |       const modelAggregator = Aggregator.formatModelConnectionsGQL( | 
					
						
							|  |  |  |         attributes, | 
					
						
							|  |  |  |         model, | 
					
						
							|  |  |  |         name, | 
					
						
							| 
									
										
										
										
											2019-11-28 18:29:35 +01:00
										 |  |  |         queries.plural, | 
					
						
							|  |  |  |         plugin | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |       ); | 
					
						
							|  |  |  |       if (modelAggregator) { | 
					
						
							|  |  |  |         acc.definition += modelAggregator.type; | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |         if (!acc.resolvers[modelAggregator.globalId]) { | 
					
						
							|  |  |  |           acc.resolvers[modelAggregator.globalId] = {}; | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-28 15:33:37 +01:00
										 |  |  |         _.merge(acc.resolvers, modelAggregator.resolver); | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |         _.merge(acc.query, modelAggregator.query); | 
					
						
							| 
									
										
										
										
											2019-03-13 19:27:18 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     // Build associations queries.
 | 
					
						
							| 
									
										
										
										
											2019-12-10 16:21:21 +01:00
										 |  |  |     _.assign(acc.resolvers[globalId], buildAssocResolvers(model)); | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 16:37:46 +01:00
										 |  |  |     return acc; | 
					
						
							|  |  |  |   }, initialState); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |   buildShadowCRUD, | 
					
						
							| 
									
										
										
										
											2019-07-17 15:45:26 +02:00
										 |  |  |   buildModel, | 
					
						
							| 
									
										
										
										
											2018-09-10 16:05:00 +08:00
										 |  |  | }; |