diff --git a/README.md b/README.md index 0972307..70a55b1 100644 --- a/README.md +++ b/README.md @@ -92,13 +92,25 @@ It's easy to restrict your requests using Sequelize's `where` query option. Just Team.findOne({ where: { city: 'windsor' }}) ``` +You can also do more complex queries by setting the value of a key to JSON. + +```js +// returns only teams that have a `address.city` property of "windsor" +// GET /team?city={"address": "windsor"} +// or +// GET /team?city[address]=windsor + +// results in the Sequelize query: +Team.findOne({ where: { address: { city: 'windsor' }}}) +``` + ## `include` queries Getting related models is easy, just use a query parameter `include`. ```js // returns all teams with their related City model // GET /teams?include=city or -// GET /teams?include={"include": {"model": "City"}}} +// GET /teams?include={"model": "City"} // results in a Sequelize query: @@ -126,7 +138,7 @@ Team.findAll({include: [Player]}) Filtering by related models property, you can pass **where** paremeter inside each **include** item(s) object. ```js // returns all team with their related City where City property name equals Healdsburg -// GET /teams?include={"include": {"model": "City", "where": {"name": "Healdsburg"}}} +// GET /teams?include={"model": "City", "where": {"name": "Healdsburg"}} // results in a Sequelize query: Team.findAll({include: {model: City, where: {name: 'Healdsburg'}}}) diff --git a/src/crud.js b/src/crud.js index d7bbc7f..3037a2e 100644 --- a/src/crud.js +++ b/src/crud.js @@ -5,16 +5,16 @@ import _ from 'lodash'; import { parseInclude, parseWhere, parseLimitAndOffset, parseOrder } from './utils'; import { notFound } from 'boom'; import * as associations from './associations/index'; -import getConfigForMethod from './get-config-for-method.js'; +import getConfigForMethod, { sequelizeOperators } from './get-config-for-method.js'; const createAll = ({ - server, - model, - prefix, - config, - attributeValidation, - modelAssociations, - scopes, + server, + model, + prefix, + config, + attributeValidation, + associationValidation, + scopes, }) => { Object.keys(methods).forEach((method) => { methods[method]({ @@ -24,7 +24,7 @@ const createAll = ({ config: getConfigForMethod({ method, attributeValidation, - modelAssociations, + associationValidation, config, scopes, }), @@ -71,6 +71,27 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi return params; }, {}); + const modelsHasAssociations = modelAssociations && modelAssociations.length; + const validAssociationsString = modelsHasAssociations + ? joi.string().valid(...modelAssociations) + : joi.valid(null); + const validAssociationsObject = modelsHasAssociations + ? joi.object().keys({ + model: joi.string().valid(...modelAssociations), + where: joi.object().keys({ + ...attributeValidation, + ...sequelizeOperators, + }), + }) + : joi.valid(null); + const associationValidation = { + include: [ + joi.array().items(validAssociationsString), + joi.array().items(validAssociationsObject), + validAssociationsString, + validAssociationsObject, + ], + }; const scopes = Object.keys(model.options.scopes); // if we don't have any permissions set, just create all the methods @@ -81,7 +102,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi prefix, config, attributeValidation, - modelAssociations, + associationValidation, scopes, }); // if permissions are set, but we can't parse them, throw an error @@ -96,7 +117,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi prefix, config, attributeValidation, - modelAssociations, + associationValidation, scopes, }); // if we've gotten here, we have complex permissions and need to set them @@ -118,7 +139,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi config: getConfigForMethod({ method, attributeValidation, - modelAssociations, + associationValidation, scopes, config: permissionConfig, }), @@ -130,7 +151,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi model, prefix, attributeValidation, - modelAssociations, + associationValidation, scopes, config: permissionConfig, }); @@ -248,8 +269,8 @@ export const destroy = ({ server, model, prefix = '/', config }) => { if (!list.length) { return void reply(id - ? notFound(`${id} not found.`) - : notFound('Nothing found.') + ? notFound(`${id} not found.`) + : notFound('Nothing found.') ); } @@ -277,8 +298,8 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => { if (!list.length) { return void reply(id - ? notFound(`${id} not found.`) - : notFound('Nothing found.') + ? notFound(`${id} not found.`) + : notFound('Nothing found.') ); } diff --git a/src/get-config-for-method.js b/src/get-config-for-method.js index 0c3a28b..d6bc661 100644 --- a/src/get-config-for-method.js +++ b/src/get-config-for-method.js @@ -72,7 +72,7 @@ export const restrictMethods = [ ]; export default ({ - method, attributeValidation, modelAssociations, scopes = [], config = {}, + method, attributeValidation, associationValidation, scopes = [], config = {}, }) => { const hasWhere = whereMethods.includes(method); const hasInclude = includeMethods.includes(method); @@ -96,27 +96,9 @@ export default ({ } if (hasInclude) { - const modelsHasAssociations = modelAssociations && modelAssociations.length; - const validAssociationsString = modelsHasAssociations - ? joi.string().valid(...modelAssociations) - : joi.valid(null); - const validAssociationsObject = modelsHasAssociations - ? joi.object().keys({ - model: joi.string().valid(...modelAssociations), - where: joi.object().keys({ - ...attributeValidation, - ...sequelizeOperators, - }), - }) - : joi.valid(null); const query = concatToJoiObject(joi.object() .keys({ - include: [ - joi.array().items(validAssociationsString), - joi.array().items(validAssociationsObject), - validAssociationsString, - validAssociationsObject, - ], + ...associationValidation, }), get(methodConfig, 'validate.query') ); diff --git a/src/get-config-for-method.test.js b/src/get-config-for-method.test.js index 739bf8d..ea8edad 100644 --- a/src/get-config-for-method.test.js +++ b/src/get-config-for-method.test.js @@ -20,19 +20,8 @@ test.beforeEach((t) => { myKey: joi.any(), }; - const validAssociationsString = joi.string().valid(...t.context.models); - const validAssociationsObject = joi.object().keys({ - model: joi.string().valid(...t.context.models), - where: joi.object(), - }); - t.context.associationValidation = { - include: [ - joi.array().items(validAssociationsString), - joi.array().items(validAssociationsObject), - validAssociationsString, - validAssociationsObject, - ], + include: joi.array().items(joi.string().valid(t.context.models)), }; t.context.config = { @@ -162,12 +151,12 @@ test('query attributeValidation w/ config as joi object', (t) => { test('validate.query associationValidation', (t) => { const { attributeValidation, associationValidation, models } = t.context; - const modelAssociations = models; + includeMethods.forEach((method) => { const configForMethod = getConfigForMethod({ method, attributeValidation, - modelAssociations, + associationValidation, }); const { query } = configForMethod.validate; @@ -194,7 +183,6 @@ test('validate.query associationValidation', (t) => { test('query associationValidation w/ config as plain object', (t) => { const { associationValidation, models } = t.context; - const modelAssociations = models; const config = { validate: { query: { @@ -206,7 +194,7 @@ test('query associationValidation w/ config as plain object', (t) => { includeMethods.forEach((method) => { const configForMethod = getConfigForMethod({ method, - modelAssociations, + associationValidation, config, }); const { query } = configForMethod.validate; @@ -234,7 +222,6 @@ test('query associationValidation w/ config as plain object', (t) => { test('query associationValidation w/ config as joi object', (t) => { const { associationValidation, models } = t.context; - const modelAssociations = models; const queryKeys = { aKey: joi.boolean(), }; @@ -247,7 +234,7 @@ test('query associationValidation w/ config as joi object', (t) => { includeMethods.forEach((method) => { const configForMethod = getConfigForMethod({ method, - modelAssociations, + associationValidation, config, }); const { query } = configForMethod.validate;