Add feature to allow nested include and filtering relationships/associations #36
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								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' }})
 | 
					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
 | 
					## `include` queries
 | 
				
			||||||
Getting related models is easy, just use a query parameter `include`.
 | 
					Getting related models is easy, just use a query parameter `include`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
// returns all teams with their related City model
 | 
					// returns all teams with their related City model
 | 
				
			||||||
// GET /teams?include=city or
 | 
					// GET /teams?include=city or
 | 
				
			||||||
// GET /teams?include={"include": {"model": "City"}}}
 | 
					// GET /teams?include={"model": "City"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// results in a Sequelize query:
 | 
					// 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.
 | 
					Filtering by related models property, you can pass **where** paremeter inside each **include** item(s) object.
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
// returns all team with their related City where City property name equals Healdsburg
 | 
					// 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:
 | 
					// results in a Sequelize query:
 | 
				
			||||||
Team.findAll({include: {model: City, where: {name: 'Healdsburg'}}})
 | 
					Team.findAll({include: {model: City, where: {name: 'Healdsburg'}}})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								src/crud.js
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/crud.js
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ import _ from 'lodash';
 | 
				
			|||||||
import { parseInclude, parseWhere, parseLimitAndOffset, parseOrder } from './utils';
 | 
					import { parseInclude, parseWhere, parseLimitAndOffset, parseOrder } from './utils';
 | 
				
			||||||
import { notFound } from 'boom';
 | 
					import { notFound } from 'boom';
 | 
				
			||||||
import * as associations from './associations/index';
 | 
					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 = ({
 | 
					const createAll = ({
 | 
				
			||||||
    server,
 | 
					    server,
 | 
				
			||||||
@@ -13,7 +13,7 @@ const createAll = ({
 | 
				
			|||||||
    prefix,
 | 
					    prefix,
 | 
				
			||||||
    config,
 | 
					    config,
 | 
				
			||||||
    attributeValidation,
 | 
					    attributeValidation,
 | 
				
			||||||
  modelAssociations,
 | 
					    associationValidation,
 | 
				
			||||||
    scopes,
 | 
					    scopes,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  Object.keys(methods).forEach((method) => {
 | 
					  Object.keys(methods).forEach((method) => {
 | 
				
			||||||
@@ -24,7 +24,7 @@ const createAll = ({
 | 
				
			|||||||
      config: getConfigForMethod({
 | 
					      config: getConfigForMethod({
 | 
				
			||||||
        method,
 | 
					        method,
 | 
				
			||||||
        attributeValidation,
 | 
					        attributeValidation,
 | 
				
			||||||
        modelAssociations,
 | 
					        associationValidation,
 | 
				
			||||||
        config,
 | 
					        config,
 | 
				
			||||||
        scopes,
 | 
					        scopes,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
@@ -71,6 +71,27 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
 | 
				
			|||||||
    return params;
 | 
					    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);
 | 
					  const scopes = Object.keys(model.options.scopes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // if we don't have any permissions set, just create all the methods
 | 
					  // 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,
 | 
					      prefix,
 | 
				
			||||||
      config,
 | 
					      config,
 | 
				
			||||||
      attributeValidation,
 | 
					      attributeValidation,
 | 
				
			||||||
      modelAssociations,
 | 
					      associationValidation,
 | 
				
			||||||
      scopes,
 | 
					      scopes,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // if permissions are set, but we can't parse them, throw an error
 | 
					    // 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,
 | 
					      prefix,
 | 
				
			||||||
      config,
 | 
					      config,
 | 
				
			||||||
      attributeValidation,
 | 
					      attributeValidation,
 | 
				
			||||||
      modelAssociations,
 | 
					      associationValidation,
 | 
				
			||||||
      scopes,
 | 
					      scopes,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // if we've gotten here, we have complex permissions and need to set them
 | 
					    // 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({
 | 
					              config: getConfigForMethod({
 | 
				
			||||||
                method,
 | 
					                method,
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 | 
				|||||||
                attributeValidation,
 | 
					                attributeValidation,
 | 
				
			||||||
                modelAssociations,
 | 
					                associationValidation,
 | 
				
			||||||
                scopes,
 | 
					                scopes,
 | 
				
			||||||
                config: permissionConfig,
 | 
					                config: permissionConfig,
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
@@ -130,7 +151,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
 | 
				
			|||||||
            model,
 | 
					            model,
 | 
				
			||||||
            prefix,
 | 
					            prefix,
 | 
				
			||||||
            attributeValidation,
 | 
					            attributeValidation,
 | 
				
			||||||
            modelAssociations,
 | 
					            associationValidation,
 | 
				
			||||||
            scopes,
 | 
					            scopes,
 | 
				
			||||||
            config: permissionConfig,
 | 
					            config: permissionConfig,
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 @joeybaker and @mdibaiee, i need help to apply include child joi validation to be the same as its parent include validation (nested validations with the same schema) @joeybaker and @mdibaiee, i need help to apply include child joi validation to be the same as its parent include validation (nested validations with the same schema)
 
			
			
		 | 
				|||||||
          });
 | 
					          });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,7 +72,7 @@ export const restrictMethods = [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default ({
 | 
					export default ({
 | 
				
			||||||
  method, attributeValidation, modelAssociations, scopes = [], config = {},
 | 
					  method, attributeValidation, associationValidation, scopes = [], config = {},
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const hasWhere = whereMethods.includes(method);
 | 
					  const hasWhere = whereMethods.includes(method);
 | 
				
			||||||
  const hasInclude = includeMethods.includes(method);
 | 
					  const hasInclude = includeMethods.includes(method);
 | 
				
			||||||
@@ -96,27 +96,9 @@ export default ({
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (hasInclude) {
 | 
					  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()
 | 
					    const query = concatToJoiObject(joi.object()
 | 
				
			||||||
      .keys({
 | 
					      .keys({
 | 
				
			||||||
        include: [
 | 
					        ...associationValidation,
 | 
				
			||||||
          joi.array().items(validAssociationsString),
 | 
					 | 
				
			||||||
          joi.array().items(validAssociationsObject),
 | 
					 | 
				
			||||||
          validAssociationsString,
 | 
					 | 
				
			||||||
          validAssociationsObject,
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      get(methodConfig, 'validate.query')
 | 
					      get(methodConfig, 'validate.query')
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,19 +20,8 @@ test.beforeEach((t) => {
 | 
				
			|||||||
    myKey: joi.any(),
 | 
					    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 = {
 | 
					  t.context.associationValidation = {
 | 
				
			||||||
    include: [
 | 
					    include: joi.array().items(joi.string().valid(t.context.models)),
 | 
				
			||||||
      joi.array().items(validAssociationsString),
 | 
					 | 
				
			||||||
      joi.array().items(validAssociationsObject),
 | 
					 | 
				
			||||||
      validAssociationsString,
 | 
					 | 
				
			||||||
      validAssociationsObject,
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  t.context.config = {
 | 
					  t.context.config = {
 | 
				
			||||||
@@ -162,12 +151,12 @@ test('query attributeValidation w/ config as joi object', (t) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
test('validate.query associationValidation', (t) => {
 | 
					test('validate.query associationValidation', (t) => {
 | 
				
			||||||
  const { attributeValidation, associationValidation, models } = t.context;
 | 
					  const { attributeValidation, associationValidation, models } = t.context;
 | 
				
			||||||
  const modelAssociations = models;
 | 
					
 | 
				
			||||||
  includeMethods.forEach((method) => {
 | 
					  includeMethods.forEach((method) => {
 | 
				
			||||||
    const configForMethod = getConfigForMethod({
 | 
					    const configForMethod = getConfigForMethod({
 | 
				
			||||||
      method,
 | 
					      method,
 | 
				
			||||||
      attributeValidation,
 | 
					      attributeValidation,
 | 
				
			||||||
      modelAssociations,
 | 
					      associationValidation,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const { query } = configForMethod.validate;
 | 
					    const { query } = configForMethod.validate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -194,7 +183,6 @@ test('validate.query associationValidation', (t) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
test('query associationValidation w/ config as plain object', (t) => {
 | 
					test('query associationValidation w/ config as plain object', (t) => {
 | 
				
			||||||
  const { associationValidation, models } = t.context;
 | 
					  const { associationValidation, models } = t.context;
 | 
				
			||||||
  const modelAssociations = models;
 | 
					 | 
				
			||||||
  const config = {
 | 
					  const config = {
 | 
				
			||||||
    validate: {
 | 
					    validate: {
 | 
				
			||||||
      query: {
 | 
					      query: {
 | 
				
			||||||
@@ -206,7 +194,7 @@ test('query associationValidation w/ config as plain object', (t) => {
 | 
				
			|||||||
  includeMethods.forEach((method) => {
 | 
					  includeMethods.forEach((method) => {
 | 
				
			||||||
    const configForMethod = getConfigForMethod({
 | 
					    const configForMethod = getConfigForMethod({
 | 
				
			||||||
      method,
 | 
					      method,
 | 
				
			||||||
      modelAssociations,
 | 
					      associationValidation,
 | 
				
			||||||
      config,
 | 
					      config,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const { query } = configForMethod.validate;
 | 
					    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) => {
 | 
					test('query associationValidation w/ config as joi object', (t) => {
 | 
				
			||||||
  const { associationValidation, models } = t.context;
 | 
					  const { associationValidation, models } = t.context;
 | 
				
			||||||
  const modelAssociations = models;
 | 
					 | 
				
			||||||
  const queryKeys = {
 | 
					  const queryKeys = {
 | 
				
			||||||
    aKey: joi.boolean(),
 | 
					    aKey: joi.boolean(),
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -247,7 +234,7 @@ test('query associationValidation w/ config as joi object', (t) => {
 | 
				
			|||||||
  includeMethods.forEach((method) => {
 | 
					  includeMethods.forEach((method) => {
 | 
				
			||||||
    const configForMethod = getConfigForMethod({
 | 
					    const configForMethod = getConfigForMethod({
 | 
				
			||||||
      method,
 | 
					      method,
 | 
				
			||||||
      modelAssociations,
 | 
					      associationValidation,
 | 
				
			||||||
      config,
 | 
					      config,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const { query } = configForMethod.validate;
 | 
					    const { query } = configForMethod.validate;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user
	
Why move this validation into
get-config-for-method? All other validation is defined in this method already.Sorry @joeybaker i have fixed this, moved to crud.js