Added feature to allow filtering relationships/associations based on http://docs.sequelizejs.com/en/latest/docs/querying/#relations-associations

This commit is contained in:
Muhammad Labib Ramadhan 2016-11-04 11:31:56 +07:00
parent 5ba9d7d261
commit 72452a0088
4 changed files with 63 additions and 39 deletions

View File

@ -85,3 +85,13 @@ test('multiple includes /team?include[]=players&include[]=city', async (t) => {
t.truthy(playerIds.includes(player2.id)); t.truthy(playerIds.includes(player2.id));
t.is(result.City.id, city1.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);
});

View File

@ -13,7 +13,7 @@ const createAll = ({
prefix, prefix,
config, config,
attributeValidation, attributeValidation,
associationValidation, modelAssociations,
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,
associationValidation, modelAssociations,
config, config,
scopes, scopes,
}), }),
@ -35,22 +35,22 @@ const createAll = ({
export { associations }; 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: { models: {
cat: ['list', 'get'] cat: ['list', 'get']
, dog: true // all , dog: true // all
} }
``` ```
*/ */
export default (server, model, { prefix, defaultConfig: config, models: permissions }) => { export default (server, model, { prefix, defaultConfig: config, models: permissions }) => {
const modelName = model._singular; const modelName = model._singular;
@ -71,13 +71,6 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
return params; 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); 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
@ -88,14 +81,14 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
prefix, prefix,
config, config,
attributeValidation, attributeValidation,
associationValidation, modelAssociations,
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
} else if (!Array.isArray(permissions)) { } else if (!Array.isArray(permissions)) {
throw new Error('hapi-sequelize-crud: `models` property must be an array'); 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 // 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 // are no permissions to be set, so just create all methods and move on
} else if (permissions.includes(modelName)) { } else if (permissions.includes(modelName)) {
createAll({ createAll({
server, server,
@ -103,10 +96,10 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
prefix, prefix,
config, config,
attributeValidation, attributeValidation,
associationValidation, modelAssociations,
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
} else { } else {
const permissionOptions = permissions.filter((permission) => { const permissionOptions = permissions.filter((permission) => {
return permission.model === modelName; return permission.model === modelName;
@ -125,7 +118,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
config: getConfigForMethod({ config: getConfigForMethod({
method, method,
attributeValidation, attributeValidation,
associationValidation, modelAssociations,
scopes, scopes,
config: permissionConfig, config: permissionConfig,
}), }),
@ -137,7 +130,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
model, model,
prefix, prefix,
attributeValidation, attributeValidation,
associationValidation, modelAssociations,
scopes, scopes,
config: permissionConfig, config: permissionConfig,
}); });
@ -257,7 +250,7 @@ export const destroy = ({ server, model, prefix = '/', config }) => {
return void reply(id return void reply(id
? notFound(`${id} not found.`) ? notFound(`${id} not found.`)
: notFound('Nothing found.') : notFound('Nothing found.')
); );
} }
await Promise.all(list.map(instance => instance.destroy())); await Promise.all(list.map(instance => instance.destroy()));
@ -286,7 +279,7 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => {
return void reply(id return void reply(id
? notFound(`${id} not found.`) ? notFound(`${id} not found.`)
: notFound('Nothing found.') : notFound('Nothing found.')
); );
} }
await Promise.all(list.map(instance => instance.destroy())); await Promise.all(list.map(instance => instance.destroy()));

View File

@ -72,7 +72,7 @@ export const restrictMethods = [
]; ];
export default ({ export default ({
method, attributeValidation, associationValidation, scopes = [], config = {}, method, attributeValidation, modelAssociations, scopes = [], config = {},
}) => { }) => {
const hasWhere = whereMethods.includes(method); const hasWhere = whereMethods.includes(method);
const hasInclude = includeMethods.includes(method); const hasInclude = includeMethods.includes(method);
@ -96,9 +96,27 @@ 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({
...associationValidation, include: [
joi.array().items(validAssociationsString),
joi.array().items(validAssociationsObject),
validAssociationsString,
validAssociationsObject,
],
}), }),
get(methodConfig, 'validate.query') get(methodConfig, 'validate.query')
); );

View File

@ -25,13 +25,16 @@ export const parseInclude = request => {
const models = getModels(request); const models = getModels(request);
if (models.isBoom) return models; if (models.isBoom) return models;
return include.map(a => { return include.map(b => {
const singluarOrPluralMatch = Object.keys(models).find((modelName) => { const a = /^{.*}$/.test(b) ? JSON.parse(b) : b;
const { _singular, _plural } = models[modelName]; if (typeof a !== 'object') {
return _singular === a || _plural === a; 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]; if (typeof a === 'string') return models[a];