Add nested resolvers filtering & fix nested component load

This commit is contained in:
Convly 2021-08-09 18:40:25 +02:00
parent 5aece86258
commit 513f66ce9b
6 changed files with 126 additions and 44 deletions

View File

@ -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 });

View File

@ -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 });
}; };
/** /**

View File

@ -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,

View File

@ -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);
}; };
}; };

View File

@ -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 };

View File

@ -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,
}; };