mirror of
https://github.com/strapi/strapi.git
synced 2025-09-07 07:41:08 +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