diff --git a/packages/plugins/graphql/services/schema/builders/queries/collection-type.js b/packages/plugins/graphql/services/schema/builders/queries/collection-type.js index 135dea1a9d..9dcb5e3dc2 100644 --- a/packages/plugins/graphql/services/schema/builders/queries/collection-type.js +++ b/packages/plugins/graphql/services/schema/builders/queries/collection-type.js @@ -3,8 +3,8 @@ const { extendType } = require('nexus'); const { actionExists } = require('../../../old/utils'); -const { utils, args } = require('../../../types'); -const { transformArgs } = require('../utils'); +const { utils } = require('../../../types'); +const { transformArgs, getContentTypeArgs } = require('../utils'); const { buildQueriesResolvers } = require('../../resolvers'); module.exports = ({ strapi }) => { @@ -35,18 +35,10 @@ module.exports = ({ strapi }) => { 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, { type: responseTypeName, - args: { - id: 'ID', - // todo[v4]: Don't allow to filter using every unique attributes for now - // ...uniqueAttributes, - }, + args: getContentTypeArgs(contentType, { multiple: false }), async resolve(source, args) { const transformedArgs = transformArgs(args, { contentType }); @@ -80,14 +72,7 @@ module.exports = ({ strapi }) => { t.field(findQueryName, { type: responseCollectionTypeName, - args: { - publicationState: args.PublicationStateArg, - // todo[v4]: to add through i18n plugin - locale: 'String', - sort: args.SortArg, - pagination: args.PaginationArg, - filters: utils.getFiltersInputTypeName(contentType), - }, + args: getContentTypeArgs(contentType), async resolve(source, args) { const transformedArgs = transformArgs(args, { contentType, usePagination: true }); diff --git a/packages/plugins/graphql/services/schema/builders/type.js b/packages/plugins/graphql/services/schema/builders/type.js index 51aca3230d..0c74165675 100644 --- a/packages/plugins/graphql/services/schema/builders/type.js +++ b/packages/plugins/graphql/services/schema/builders/type.js @@ -6,7 +6,8 @@ const { objectType } = require('nexus'); const { contentTypes } = require('@strapi/utils'); const { mappers, utils: typeUtils, constants } = require('../../types'); -const { buildAssociationResolver } = require('../resolvers'); +const { buildAssociationResolver, buildComponentResolver } = require('../resolvers'); +const { getContentTypeArgs } = require('./utils'); /** * @typedef TypeBuildersOptions @@ -160,22 +161,17 @@ const addComponentAttribute = ({ builder, attributeName, contentType, attribute, builder = builder.list; } - builder.field(attributeName, { - type, + const targetComponent = strapi.getModel(attribute.component); - // Move resolver to /resolvers/component.js - async resolve(source) { - const { [attributeName]: component } = await strapi.db.entityManager.populate( - contentType.uid, - source, - { - [attributeName]: true, - } - ); - - return component; - }, + const resolve = buildComponentResolver({ + contentTypeUID: contentType.uid, + attributeName, + strapi, }); + + 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 type = typeUtils.getTypeName(fileContentType); - const associationResolver = buildAssociationResolver({ + const resolve = buildAssociationResolver({ contentTypeUID: contentType.uid, attributeName, 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; } - const associationResolver = buildAssociationResolver({ + const resolve = buildAssociationResolver({ contentTypeUID: contentType.uid, attributeName, strapi, @@ -265,7 +263,7 @@ const addPolymorphicRelationalAttribute = options => { if (isUndefined(target)) { builder.field(attributeName, { type: constants.GENERIC_MORPH_TYPENAME, - resolve: associationResolver, + resolve, }); } @@ -273,7 +271,7 @@ const addPolymorphicRelationalAttribute = options => { else if (isArray(target) && target.every(isString)) { 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; } - const associationResolver = buildAssociationResolver({ + const resolve = buildAssociationResolver({ contentTypeUID: contentType.uid, attributeName, strapi, @@ -305,7 +303,9 @@ const addRegularRelationalAttribute = options => { const targetContentType = strapi.getModel(attribute.target); const type = typeUtils.getTypeName(targetContentType); - builder.field(attributeName, { type, resolve: associationResolver }); + const args = isToManyRelation ? getContentTypeArgs(targetContentType) : undefined; + + builder.field(attributeName, { type, resolve, args }); }; /** diff --git a/packages/plugins/graphql/services/schema/builders/utils.js b/packages/plugins/graphql/services/schema/builders/utils.js index 3765e18515..6c44d43fe5 100644 --- a/packages/plugins/graphql/services/schema/builders/utils.js +++ b/packages/plugins/graphql/services/schema/builders/utils.js @@ -6,8 +6,9 @@ const { } = require('@strapi/utils'); const { + args, mappers: { strapiScalarToGraphQLScalar, graphQLFiltersToStrapiQuery }, - utils: { isScalar, getScalarFilterInputTypeName }, + utils: { isScalar, getScalarFilterInputTypeName, getFiltersInputTypeName }, } = require('../../types'); /** @@ -38,7 +39,7 @@ const scalarAttributesToFiltersMap = mapValues(attribute => { * Apply basic transform to GQL args */ // todo[v4]: unify & move elsewhere -const transformArgs = (args, { contentType, usePagination = false }) => { +const transformArgs = (args, { contentType, usePagination = false } = {}) => { const { pagination = {}, filters = {} } = args; // Init @@ -60,7 +61,49 @@ const transformArgs = (args, { contentType, usePagination = false }) => { 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 = { + getContentTypeArgs, getUniqueScalarAttributes, scalarAttributesToFiltersMap, transformArgs, diff --git a/packages/plugins/graphql/services/schema/resolvers/association.js b/packages/plugins/graphql/services/schema/resolvers/association.js index 63514a234d..4664bc349d 100644 --- a/packages/plugins/graphql/services/schema/resolvers/association.js +++ b/packages/plugins/graphql/services/schema/resolvers/association.js @@ -1,11 +1,41 @@ 'use strict'; +const { omit } = require('lodash/fp'); + +const { transformArgs } = require('../builders/utils'); +const { utils } = require('../../types'); + const buildAssociationResolver = ({ contentTypeUID, attributeName, strapi }) => { 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 = {}) => { + 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? - return entityManager.load(contentTypeUID, parent, attributeName, args); + return entityManager.load(contentTypeUID, parent, attributeName, hotFixedArgs); }; }; diff --git a/packages/plugins/graphql/services/schema/resolvers/component.js b/packages/plugins/graphql/services/schema/resolvers/component.js new file mode 100644 index 0000000000..b38e72e091 --- /dev/null +++ b/packages/plugins/graphql/services/schema/resolvers/component.js @@ -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 }; diff --git a/packages/plugins/graphql/services/schema/resolvers/index.js b/packages/plugins/graphql/services/schema/resolvers/index.js index 6919a63bde..e295f16f30 100644 --- a/packages/plugins/graphql/services/schema/resolvers/index.js +++ b/packages/plugins/graphql/services/schema/resolvers/index.js @@ -3,6 +3,7 @@ const associationResolvers = require('./association'); const { buildQueriesResolvers } = require('./query'); const { buildMutationsResolvers } = require('./mutation'); +const { buildComponentResolver } = require('./component'); module.exports = { // Generics @@ -11,4 +12,5 @@ module.exports = { // Builders buildMutationsResolvers, buildQueriesResolvers, + buildComponentResolver, };