diff --git a/src/graphql/schema/buildWhereInputType.js b/src/graphql/schema/buildWhereInputType.js index 0592b629fb..3fbaaa3002 100644 --- a/src/graphql/schema/buildWhereInputType.js +++ b/src/graphql/schema/buildWhereInputType.js @@ -131,12 +131,12 @@ const buildWhereInputType = ({ name, fields, parent }) => { const fieldName = formatName(name); return new GraphQLInputObjectType({ - name: `${fieldName}Where`, + name: `${fieldName}_where`, fields: { ...fieldTypes, OR: { type: new GraphQLList(new GraphQLInputObjectType({ - name: `${fieldName}WhereOr`, + name: `${fieldName}_where_or`, fields: { ...fieldTypes, }, @@ -144,7 +144,7 @@ const buildWhereInputType = ({ name, fields, parent }) => { }, AND: { type: new GraphQLList(new GraphQLInputObjectType({ - name: `${fieldName}WhereAnd`, + name: `${fieldName}_where_and`, fields: { ...fieldTypes, }, diff --git a/src/graphql/schema/getBuildObjectType.js b/src/graphql/schema/getBuildObjectType.js index cc28f44b3f..162693bc5d 100644 --- a/src/graphql/schema/getBuildObjectType.js +++ b/src/graphql/schema/getBuildObjectType.js @@ -101,7 +101,7 @@ function getBuildObjectType(context) { ), }), select: (field) => { - const fullName = combineParentName(parent, field.label); + const fullName = combineParentName(parent, field.name); const type = new GraphQLEnumType({ name: fullName, diff --git a/src/graphql/schema/withOperators.js b/src/graphql/schema/withOperators.js index 8402014d57..ed592d9e86 100644 --- a/src/graphql/schema/withOperators.js +++ b/src/graphql/schema/withOperators.js @@ -2,7 +2,7 @@ const { GraphQLInputObjectType } = require('graphql'); const combineParentName = require('../utilities/combineParentName'); const withOperators = (fieldName, type, parent, operators) => { - const name = `${combineParentName(parent, fieldName)}Operator`; + const name = `${combineParentName(parent, fieldName)}_operator`; return new GraphQLInputObjectType({ name, fields: operators.reduce((fields, operator) => { diff --git a/src/mongoose/buildQuery.js b/src/mongoose/buildQuery.js index b54f8b2793..e00a636a7b 100644 --- a/src/mongoose/buildQuery.js +++ b/src/mongoose/buildQuery.js @@ -1,30 +1,27 @@ const mongoose = require('mongoose'); -const validOperators = ['like', 'in', 'all', 'nin', 'gte', 'gt', 'lte', 'lt', 'ne']; - -function addSearchParam(key, value, searchParams, schema) { - if (schema.paths[key]) { - if (typeof value === 'object') { - return { - ...searchParams, - [key]: { - ...searchParams[key], - ...value, - }, - }; - } +const validOperators = ['like', 'in', 'all', 'not_in', 'greater_than_equal', 'greater_than', 'less_than_equal', 'less_than', 'not_equal', 'equals']; +function addSearchParam(key, value, searchParams) { + if (typeof value === 'object') { return { ...searchParams, - [key]: value, + [key]: { + ...searchParams[key], + ...value, + }, }; } - return searchParams; + return { + ...searchParams, + [key]: value, + }; } class ParamParser { constructor(model, rawParams, locale) { + this.parse = this.parse.bind(this); this.model = model; this.rawParams = rawParams; this.locale = locale; @@ -41,15 +38,30 @@ class ParamParser { // Entry point to the ParamParser class async parse() { Object.keys(this.rawParams).forEach(async (key) => { - // If rawParams[key] is an object, that means there are operators present. - // Need to loop through keys on rawParams[key] to call addSearchParam on each operator found - if (typeof this.rawParams[key] === 'object') { - Object.keys(this.rawParams[key]) - .forEach(async (operator) => { - const [searchParamKey, searchParamValue] = await this.buildSearchParam(this.model.schema, key, this.rawParams[key][operator], operator); - this.query.searchParams = addSearchParam(searchParamKey, searchParamValue, this.query.searchParams, this.model.schema); - }); - // Otherwise there are no operators present + if (key === 'where') { + // We now need to determine if the whereKey is an AND, OR, or a schema path + Object.keys(this.rawParams[key]).forEach(async (rawRelationOrPath) => { + const relationOrPath = rawRelationOrPath.toLowerCase(); + + if (relationOrPath === 'and') { + this.query.searchParams = addSearchParam('$and', {}, this.query.searchParams); + } else if (relationOrPath === 'or') { + this.query.searchParams = addSearchParam('$or', {}, this.query.searchParams); + } else { + // It's a path - and there can be multiple comparisons on a single path. + // For example - title like 'test' and title not equal to 'tester' + // So we need to loop on keys again here to grab operators + + const pathWhere = this.rawParams[key][relationOrPath]; + + if (typeof pathWhere === 'object') { + Object.keys(pathWhere).forEach(async (operator) => { + const [searchParamKey, searchParamValue] = await this.buildSearchParam(this.model.schema, relationOrPath, pathWhere[operator], operator); + this.query.searchParams = addSearchParam(searchParamKey, searchParamValue, this.query.searchParams); + }); + } + } + }); } else { const [searchParamKey, searchParamValue] = await this.buildSearchParam(this.model.schema, key, this.rawParams[key]); if (searchParamKey === 'sort') { @@ -58,11 +70,12 @@ class ParamParser { this.query.searchParams = addSearchParam(searchParamKey, searchParamValue, this.query.searchParams, this.model.schema); } } - }); + }, this); return this.query; } + // Checks to see async buildSearchParam(schema, key, val, operator) { let schemaObject = schema.obj[key]; const localizedKey = this.getLocalizedKey(key, schemaObject); @@ -116,10 +129,10 @@ class ParamParser { if (operator && validOperators.includes(operator)) { switch (operator) { - case 'gte': - case 'lte': - case 'lt': - case 'gt': + case 'greater_than_equal': + case 'less_than_equal': + case 'less_than': + case 'greater_than': formattedValue = { [`$${operator}`]: val, }; @@ -128,19 +141,24 @@ class ParamParser { case 'in': case 'all': - case 'nin': + case 'not_in': formattedValue = { [`$${operator}`]: val.split(','), }; break; - default: + case 'like': formattedValue = { $regex: val, $options: '-i', }; + break; + + default: + formattedValue = val; + break; } } @@ -153,11 +171,14 @@ class ParamParser { // which can then be used in subsequent Mongoose queries. function buildQueryPlugin(schema) { const modifiedSchema = schema; - modifiedSchema.statics.apiQuery = async (rawParams, locale) => { + + async function apiQuery(rawParams, locale) { const paramParser = new ParamParser(this, rawParams, locale); const params = await paramParser.parse(); return params.searchParams; - }; + } + + modifiedSchema.statics.apiQuery = apiQuery; } module.exports = buildQueryPlugin;