mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 18:08:11 +00:00 
			
		
		
		
	Add nested resolvers filtering & fix nested component load
This commit is contained in:
		
							parent
							
								
									5aece86258
								
							
						
					
					
						commit
						513f66ce9b
					
				| @ -3,8 +3,8 @@ | |||||||
| const { extendType } = require('nexus'); | const { extendType } = require('nexus'); | ||||||
| 
 | 
 | ||||||
| const { actionExists } = require('../../../old/utils'); | const { actionExists } = require('../../../old/utils'); | ||||||
| const { utils, args } = require('../../../types'); | const { utils } = require('../../../types'); | ||||||
| const { transformArgs } = require('../utils'); | const { transformArgs, getContentTypeArgs } = require('../utils'); | ||||||
| const { buildQueriesResolvers } = require('../../resolvers'); | const { buildQueriesResolvers } = require('../../resolvers'); | ||||||
| 
 | 
 | ||||||
| module.exports = ({ strapi }) => { | module.exports = ({ strapi }) => { | ||||||
| @ -35,18 +35,10 @@ module.exports = ({ strapi }) => { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // todo[v4]: Don't allow to filter using every unique attributes for now
 |  | ||||||
|     // Only authorize filtering using unique scalar fields for findOne queries
 |  | ||||||
|     // const uniqueAttributes = getUniqueAttributesFiltersMap(attributes);
 |  | ||||||
| 
 |  | ||||||
|     t.field(findOneQueryName, { |     t.field(findOneQueryName, { | ||||||
|       type: responseTypeName, |       type: responseTypeName, | ||||||
| 
 | 
 | ||||||
|       args: { |       args: getContentTypeArgs(contentType, { multiple: false }), | ||||||
|         id: 'ID', |  | ||||||
|         // todo[v4]: Don't allow to filter using every unique attributes for now
 |  | ||||||
|         // ...uniqueAttributes,
 |  | ||||||
|       }, |  | ||||||
| 
 | 
 | ||||||
|       async resolve(source, args) { |       async resolve(source, args) { | ||||||
|         const transformedArgs = transformArgs(args, { contentType }); |         const transformedArgs = transformArgs(args, { contentType }); | ||||||
| @ -80,14 +72,7 @@ module.exports = ({ strapi }) => { | |||||||
|     t.field(findQueryName, { |     t.field(findQueryName, { | ||||||
|       type: responseCollectionTypeName, |       type: responseCollectionTypeName, | ||||||
| 
 | 
 | ||||||
|       args: { |       args: getContentTypeArgs(contentType), | ||||||
|         publicationState: args.PublicationStateArg, |  | ||||||
|         // todo[v4]: to add through i18n plugin
 |  | ||||||
|         locale: 'String', |  | ||||||
|         sort: args.SortArg, |  | ||||||
|         pagination: args.PaginationArg, |  | ||||||
|         filters: utils.getFiltersInputTypeName(contentType), |  | ||||||
|       }, |  | ||||||
| 
 | 
 | ||||||
|       async resolve(source, args) { |       async resolve(source, args) { | ||||||
|         const transformedArgs = transformArgs(args, { contentType, usePagination: true }); |         const transformedArgs = transformArgs(args, { contentType, usePagination: true }); | ||||||
|  | |||||||
| @ -6,7 +6,8 @@ const { objectType } = require('nexus'); | |||||||
| const { contentTypes } = require('@strapi/utils'); | const { contentTypes } = require('@strapi/utils'); | ||||||
| 
 | 
 | ||||||
| const { mappers, utils: typeUtils, constants } = require('../../types'); | const { mappers, utils: typeUtils, constants } = require('../../types'); | ||||||
| const { buildAssociationResolver } = require('../resolvers'); | const { buildAssociationResolver, buildComponentResolver } = require('../resolvers'); | ||||||
|  | const { getContentTypeArgs } = require('./utils'); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @typedef TypeBuildersOptions |  * @typedef TypeBuildersOptions | ||||||
| @ -160,22 +161,17 @@ const addComponentAttribute = ({ builder, attributeName, contentType, attribute, | |||||||
|     builder = builder.list; |     builder = builder.list; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   builder.field(attributeName, { |   const targetComponent = strapi.getModel(attribute.component); | ||||||
|     type, |  | ||||||
| 
 | 
 | ||||||
|     // Move resolver to /resolvers/component.js
 |   const resolve = buildComponentResolver({ | ||||||
|     async resolve(source) { |     contentTypeUID: contentType.uid, | ||||||
|       const { [attributeName]: component } = await strapi.db.entityManager.populate( |     attributeName, | ||||||
|         contentType.uid, |     strapi, | ||||||
|         source, |  | ||||||
|         { |  | ||||||
|           [attributeName]: true, |  | ||||||
|         } |  | ||||||
|       ); |  | ||||||
| 
 |  | ||||||
|       return component; |  | ||||||
|     }, |  | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   const args = getContentTypeArgs(targetComponent); | ||||||
|  | 
 | ||||||
|  |   builder.field(attributeName, { type, resolve, args }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -226,13 +222,15 @@ const addMediaAttribute = options => { | |||||||
|   const fileContentType = strapi.getModel('plugins::upload.file'); |   const fileContentType = strapi.getModel('plugins::upload.file'); | ||||||
|   const type = typeUtils.getTypeName(fileContentType); |   const type = typeUtils.getTypeName(fileContentType); | ||||||
| 
 | 
 | ||||||
|   const associationResolver = buildAssociationResolver({ |   const resolve = buildAssociationResolver({ | ||||||
|     contentTypeUID: contentType.uid, |     contentTypeUID: contentType.uid, | ||||||
|     attributeName, |     attributeName, | ||||||
|     strapi, |     strapi, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   builder.field(attributeName, { type, resolve: associationResolver }); |   const args = attribute.multiple ? getContentTypeArgs(fileContentType) : undefined; | ||||||
|  | 
 | ||||||
|  |   builder.field(attributeName, { type, resolve, args }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -255,7 +253,7 @@ const addPolymorphicRelationalAttribute = options => { | |||||||
|     builder = builder.list; |     builder = builder.list; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const associationResolver = buildAssociationResolver({ |   const resolve = buildAssociationResolver({ | ||||||
|     contentTypeUID: contentType.uid, |     contentTypeUID: contentType.uid, | ||||||
|     attributeName, |     attributeName, | ||||||
|     strapi, |     strapi, | ||||||
| @ -265,7 +263,7 @@ const addPolymorphicRelationalAttribute = options => { | |||||||
|   if (isUndefined(target)) { |   if (isUndefined(target)) { | ||||||
|     builder.field(attributeName, { |     builder.field(attributeName, { | ||||||
|       type: constants.GENERIC_MORPH_TYPENAME, |       type: constants.GENERIC_MORPH_TYPENAME, | ||||||
|       resolve: associationResolver, |       resolve, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -273,7 +271,7 @@ const addPolymorphicRelationalAttribute = options => { | |||||||
|   else if (isArray(target) && target.every(isString)) { |   else if (isArray(target) && target.every(isString)) { | ||||||
|     const type = typeUtils.getMorphRelationTypeName(contentType, attributeName); |     const type = typeUtils.getMorphRelationTypeName(contentType, attributeName); | ||||||
| 
 | 
 | ||||||
|     builder.field(attributeName, { type, resolve: associationResolver }); |     builder.field(attributeName, { type, resolve }); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -296,7 +294,7 @@ const addRegularRelationalAttribute = options => { | |||||||
|     builder = builder.list; |     builder = builder.list; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const associationResolver = buildAssociationResolver({ |   const resolve = buildAssociationResolver({ | ||||||
|     contentTypeUID: contentType.uid, |     contentTypeUID: contentType.uid, | ||||||
|     attributeName, |     attributeName, | ||||||
|     strapi, |     strapi, | ||||||
| @ -305,7 +303,9 @@ const addRegularRelationalAttribute = options => { | |||||||
|   const targetContentType = strapi.getModel(attribute.target); |   const targetContentType = strapi.getModel(attribute.target); | ||||||
|   const type = typeUtils.getTypeName(targetContentType); |   const type = typeUtils.getTypeName(targetContentType); | ||||||
| 
 | 
 | ||||||
|   builder.field(attributeName, { type, resolve: associationResolver }); |   const args = isToManyRelation ? getContentTypeArgs(targetContentType) : undefined; | ||||||
|  | 
 | ||||||
|  |   builder.field(attributeName, { type, resolve, args }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -6,8 +6,9 @@ const { | |||||||
| } = require('@strapi/utils'); | } = require('@strapi/utils'); | ||||||
| 
 | 
 | ||||||
| const { | const { | ||||||
|  |   args, | ||||||
|   mappers: { strapiScalarToGraphQLScalar, graphQLFiltersToStrapiQuery }, |   mappers: { strapiScalarToGraphQLScalar, graphQLFiltersToStrapiQuery }, | ||||||
|   utils: { isScalar, getScalarFilterInputTypeName }, |   utils: { isScalar, getScalarFilterInputTypeName, getFiltersInputTypeName }, | ||||||
| } = require('../../types'); | } = require('../../types'); | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -38,7 +39,7 @@ const scalarAttributesToFiltersMap = mapValues(attribute => { | |||||||
|  * Apply basic transform to GQL args |  * Apply basic transform to GQL args | ||||||
|  */ |  */ | ||||||
| // todo[v4]: unify & move elsewhere
 | // todo[v4]: unify & move elsewhere
 | ||||||
| const transformArgs = (args, { contentType, usePagination = false }) => { | const transformArgs = (args, { contentType, usePagination = false } = {}) => { | ||||||
|   const { pagination = {}, filters = {} } = args; |   const { pagination = {}, filters = {} } = args; | ||||||
| 
 | 
 | ||||||
|   // Init
 |   // Init
 | ||||||
| @ -60,7 +61,49 @@ const transformArgs = (args, { contentType, usePagination = false }) => { | |||||||
|   return newArgs; |   return newArgs; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Get every args for a given content type | ||||||
|  |  * @param {object} contentType | ||||||
|  |  * @param {object} options | ||||||
|  |  * @param {boolean} options.multiple | ||||||
|  |  * @return {object} | ||||||
|  |  */ | ||||||
|  | const getContentTypeArgs = (contentType, { multiple = true } = {}) => { | ||||||
|  |   const { kind, modelType } = contentType; | ||||||
|  | 
 | ||||||
|  |   // Components
 | ||||||
|  |   if (modelType === 'component') { | ||||||
|  |     return { | ||||||
|  |       sort: args.SortArg, | ||||||
|  |       pagination: args.PaginationArg, | ||||||
|  |       filters: getFiltersInputTypeName(contentType), | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Collection Types
 | ||||||
|  |   else if (kind === 'collectionType') { | ||||||
|  |     return multiple | ||||||
|  |       ? { | ||||||
|  |           publicationState: args.PublicationStateArg, | ||||||
|  |           // todo[v4]: to add through i18n plugin
 | ||||||
|  |           locale: 'String', | ||||||
|  |           sort: args.SortArg, | ||||||
|  |           pagination: args.PaginationArg, | ||||||
|  |           filters: getFiltersInputTypeName(contentType), | ||||||
|  |         } | ||||||
|  |       : { id: 'ID' }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Single Types
 | ||||||
|  |   else if (kind === 'singleType') { | ||||||
|  |     return { | ||||||
|  |       id: 'ID', | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|  |   getContentTypeArgs, | ||||||
|   getUniqueScalarAttributes, |   getUniqueScalarAttributes, | ||||||
|   scalarAttributesToFiltersMap, |   scalarAttributesToFiltersMap, | ||||||
|   transformArgs, |   transformArgs, | ||||||
|  | |||||||
| @ -1,11 +1,41 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
|  | const { omit } = require('lodash/fp'); | ||||||
|  | 
 | ||||||
|  | const { transformArgs } = require('../builders/utils'); | ||||||
|  | const { utils } = require('../../types'); | ||||||
|  | 
 | ||||||
| const buildAssociationResolver = ({ contentTypeUID, attributeName, strapi }) => { | const buildAssociationResolver = ({ contentTypeUID, attributeName, strapi }) => { | ||||||
|   const { entityManager } = strapi.db; |   const { entityManager } = strapi.db; | ||||||
| 
 | 
 | ||||||
|  |   const contentType = strapi.getModel(contentTypeUID); | ||||||
|  |   const attribute = contentType.attributes[attributeName]; | ||||||
|  | 
 | ||||||
|  |   if (!attribute) { | ||||||
|  |     throw new Error( | ||||||
|  |       `Failed to build an association resolver for ${contentTypeUID}::${attributeName}` | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // todo[v4]: make sure polymorphic relations aren't breaking here
 | ||||||
|  |   const targetUID = utils.isMedia(attribute) ? 'plugins::upload.file' : attribute.target; | ||||||
|  |   const targetContentType = strapi.getModel(targetUID); | ||||||
|  | 
 | ||||||
|   return (parent, args = {}) => { |   return (parent, args = {}) => { | ||||||
|  |     const transformedArgs = transformArgs(args, { | ||||||
|  |       contentType: targetContentType, | ||||||
|  |       usePagination: true, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // todo[v4]: move the .load to the entity service?
 | ||||||
|  |     const hotFixedArgs = { | ||||||
|  |       ...omit(['start', 'filters'], transformedArgs), | ||||||
|  |       where: transformedArgs.filters, | ||||||
|  |       offset: transformedArgs.start, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     // todo[v4]: Should we be able to run policies here too?
 |     // todo[v4]: Should we be able to run policies here too?
 | ||||||
|     return entityManager.load(contentTypeUID, parent, attributeName, args); |     return entityManager.load(contentTypeUID, parent, attributeName, hotFixedArgs); | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -0,0 +1,22 @@ | |||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | const { omit } = require('lodash/fp'); | ||||||
|  | const { transformArgs } = require('../builders/utils'); | ||||||
|  | 
 | ||||||
|  | const buildComponentResolver = ({ contentTypeUID, attributeName, strapi }) => { | ||||||
|  |   return async (source, args = {}) => { | ||||||
|  |     const contentType = strapi.getModel(contentTypeUID); | ||||||
|  |     const transformedArgs = transformArgs(args, { contentType, usePagination: true }); | ||||||
|  | 
 | ||||||
|  |     // todo[v4]: move the .load to the entity service?
 | ||||||
|  |     const hotFixedArgs = { | ||||||
|  |       ...omit(['start', 'filters'], transformedArgs), | ||||||
|  |       where: transformedArgs.filters, | ||||||
|  |       offset: transformedArgs.start, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return strapi.db.entityManager.load(contentTypeUID, source, attributeName, hotFixedArgs); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module.exports = { buildComponentResolver }; | ||||||
| @ -3,6 +3,7 @@ | |||||||
| const associationResolvers = require('./association'); | const associationResolvers = require('./association'); | ||||||
| const { buildQueriesResolvers } = require('./query'); | const { buildQueriesResolvers } = require('./query'); | ||||||
| const { buildMutationsResolvers } = require('./mutation'); | const { buildMutationsResolvers } = require('./mutation'); | ||||||
|  | const { buildComponentResolver } = require('./component'); | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   // Generics
 |   // Generics
 | ||||||
| @ -11,4 +12,5 @@ module.exports = { | |||||||
|   // Builders
 |   // Builders
 | ||||||
|   buildMutationsResolvers, |   buildMutationsResolvers, | ||||||
|   buildQueriesResolvers, |   buildQueriesResolvers, | ||||||
|  |   buildComponentResolver, | ||||||
| }; | }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Convly
						Convly