Added feature to allow filtering relationships/associations based on http://docs.sequelizejs.com/en/latest/docs/querying/#relations-associations
This commit is contained in:
parent
5ba9d7d261
commit
72452a0088
@ -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);
|
||||||
|
});
|
55
src/crud.js
55
src/crud.js
@ -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()));
|
||||||
|
@ -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')
|
||||||
);
|
);
|
||||||
|
15
src/utils.js
15
src/utils.js
@ -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];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user