diff --git a/src/crud-include.integration.test.js b/src/crud-include.integration.test.js index 169781f..797d89a 100644 --- a/src/crud-include.integration.test.js +++ b/src/crud-include.integration.test.js @@ -85,3 +85,13 @@ test('multiple includes /team?include[]=players&include[]=city', async (t) => { t.truthy(playerIds.includes(player2.id)); t.is(result.City.id, city1.id); }); + +test('inlcude filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}' + , async (t) => { + const { server } = t.context; + const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'; + const method = 'GET'; + + const { statusCode } = await server.inject({ url, method }); + t.is(statusCode, STATUS_OK); + }); \ No newline at end of file diff --git a/src/crud.js b/src/crud.js index be5cc6a..d7bbc7f 100644 --- a/src/crud.js +++ b/src/crud.js @@ -13,7 +13,7 @@ const createAll = ({ prefix, config, attributeValidation, - associationValidation, + modelAssociations, scopes, }) => { Object.keys(methods).forEach((method) => { @@ -24,7 +24,7 @@ const createAll = ({ config: getConfigForMethod({ method, attributeValidation, - associationValidation, + modelAssociations, config, scopes, }), @@ -35,22 +35,22 @@ const createAll = ({ export { associations }; /* -The `models` option, becomes `permissions`, and can look like: + The `models` option, becomes `permissions`, and can look like: -``` -models: ['cat', 'dog'] -``` + ``` + models: ['cat', 'dog'] + ``` -or + or -``` -models: { - cat: ['list', 'get'] - , dog: true // all -} -``` + ``` + models: { + cat: ['list', 'get'] + , dog: true // all + } + ``` -*/ + */ export default (server, model, { prefix, defaultConfig: config, models: permissions }) => { const modelName = model._singular; @@ -71,13 +71,6 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi return params; }, {}); - const validAssociations = modelAssociations.length - ? joi.string().valid(...modelAssociations) - : joi.valid(null); - const associationValidation = { - include: [joi.array().items(validAssociations), validAssociations], - }; - const scopes = Object.keys(model.options.scopes); // if we don't have any permissions set, just create all the methods @@ -88,14 +81,14 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi prefix, config, attributeValidation, - associationValidation, + modelAssociations, 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 } else if (!Array.isArray(permissions)) { throw new Error('hapi-sequelize-crud: `models` property must be an array'); - // if permissions are set, but the only thing we've got is a model name, there - // are no permissions to be set, so just create all methods and move on + // if permissions are set, but the only thing we've got is a model name, there + // are no permissions to be set, so just create all methods and move on } else if (permissions.includes(modelName)) { createAll({ server, @@ -103,10 +96,10 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi prefix, config, attributeValidation, - associationValidation, + modelAssociations, 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 } else { const permissionOptions = permissions.filter((permission) => { return permission.model === modelName; @@ -125,7 +118,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi config: getConfigForMethod({ method, attributeValidation, - associationValidation, + modelAssociations, scopes, config: permissionConfig, }), @@ -137,7 +130,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi model, prefix, attributeValidation, - associationValidation, + modelAssociations, scopes, config: permissionConfig, }); @@ -257,7 +250,7 @@ export const destroy = ({ server, model, prefix = '/', config }) => { return void reply(id ? notFound(`${id} not found.`) : notFound('Nothing found.') - ); + ); } await Promise.all(list.map(instance => instance.destroy())); @@ -286,7 +279,7 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => { return void reply(id ? notFound(`${id} not found.`) : notFound('Nothing found.') - ); + ); } await Promise.all(list.map(instance => instance.destroy())); diff --git a/src/get-config-for-method.js b/src/get-config-for-method.js index d6bc661..0c3a28b 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, associationValidation, scopes = [], config = {}, + method, attributeValidation, modelAssociations, scopes = [], config = {}, }) => { const hasWhere = whereMethods.includes(method); const hasInclude = includeMethods.includes(method); @@ -96,9 +96,27 @@ 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({ - ...associationValidation, + include: [ + joi.array().items(validAssociationsString), + joi.array().items(validAssociationsObject), + validAssociationsString, + validAssociationsObject, + ], }), get(methodConfig, 'validate.query') ); diff --git a/src/utils.js b/src/utils.js index 6f547b5..ad80096 100644 --- a/src/utils.js +++ b/src/utils.js @@ -25,13 +25,16 @@ export const parseInclude = request => { const models = getModels(request); if (models.isBoom) return models; - return include.map(a => { - const singluarOrPluralMatch = Object.keys(models).find((modelName) => { - const { _singular, _plural } = models[modelName]; - return _singular === a || _plural === a; - }); + return include.map(b => { + const a = /^{.*}$/.test(b) ? JSON.parse(b) : b; + if (typeof a !== 'object') { + const singluarOrPluralMatch = Object.keys(models).find((modelName) => { + const { _singular, _plural } = models[modelName]; + return _singular === a || _plural === a; + }); - if (singluarOrPluralMatch) return models[singluarOrPluralMatch]; + if (singluarOrPluralMatch) return models[singluarOrPluralMatch]; + } if (typeof a === 'string') return models[a];