6 Commits

Author SHA1 Message Date
7c2b146eed 2.5.0 2016-07-21 18:02:09 -07:00
a54683e29a 2.4.0 2016-07-21 18:02:04 -07:00
a855665777 2.3.0 2016-07-21 18:01:54 -07:00
034287672c 2.2.0 2016-07-21 18:00:38 -07:00
e8c0e61c6b Fork to @getable 2016-07-21 18:00:38 -07:00
a64a55af0d Add: permissions
It's now possible to limit the models rest routes are created for. This
is done via a `models` option that can be simple to complex. The readme
has been updated to reflect this.
2016-07-21 18:00:38 -07:00
6 changed files with 55 additions and 125 deletions

View File

@ -1,7 +0,0 @@
Commit Message
===============
Please follow [this convention](http://karma-runner.github.io/1.0/dev/git-commit-msg.html) for git commit message.
Lint
====
Please lint your code using `npm run lint` (also `npm run lint -- --fix` to auto-fix).

View File

@ -30,25 +30,17 @@ await register({
prefix: '/v1', prefix: '/v1',
name: 'db', // the same name you used for configuring `hapi-sequelize` (options.name) name: 'db', // the same name you used for configuring `hapi-sequelize` (options.name)
defaultConfig: { ... }, // passed as `config` to all routes created defaultConfig: { ... }, // passed as `config` to all routes created
// You can specify which models must have routes defined for using the
// `models` property. If you omit this property, all models will have
// models defined for them. e.g.
models: ['cat', 'dog'] // only the cat and dog models will have routes created models: ['cat', 'dog'] // only the cat and dog models will have routes created
// or // or
models: [ models: {
// possible methods: list, get, scope, create, destroy, destroyAll, destroyScope, update // possible methods: list, get, scope, create, destroy, destroyAll, destroyScope, update
// the cat model only has get and list methods enabled cat: ['get', 'list'], // the cat model only has get and list methods enabled
{model: 'cat', methods: ['get', 'list']}, dog: true, // the dog model has all methods enabled
// the dog model has all methods enabled bat: {
{model: 'dog'}, methods: ['list'],
// the cow model also has all methods enabled config: { ... } // if provided, overrides the default config
'cow', }
// the bat model as a custom config for the list method, but uses the default config for create. }
// `config` if provided, overrides the default config
{model: 'bat', methods: ['list'], config: { ... }},
{model: 'bat', methods: ['create']}
]
} }
}); });
``` ```

View File

@ -1,6 +1,6 @@
{ {
"name": "hapi-sequelize-crud", "name": "@getable/hapi-sequelize-crud",
"version": "2.5.4", "version": "2.5.0",
"description": "Hapi plugin that automatically generates RESTful API for CRUD", "description": "Hapi plugin that automatically generates RESTful API for CRUD",
"main": "build/index.js", "main": "build/index.js",
"config": { "config": {
@ -15,7 +15,7 @@
"watch": "scripty" "watch": "scripty"
}, },
"repository": { "repository": {
"git": "https://github.com/mdibaiee/hapi-sequelize-crud" "git": "https://github.com/Getable/hapi-sequelize-crud"
}, },
"files": [ "files": [
"build" "build"
@ -35,11 +35,9 @@
"scripty": "^1.6.0" "scripty": "^1.6.0"
}, },
"dependencies": { "dependencies": {
"babel": "5.8.3",
"boom": "^3.2.2", "boom": "^3.2.2",
"joi": "7.2.1", "joi": "7.2.1",
"lodash": "4.0.0" "lodash": "4.0.0"
},
"optionalDependencies": {
"babel-polyfill": "^6.13.0"
} }
} }

View File

@ -3,16 +3,13 @@ import error from './error';
import _ from 'lodash'; import _ from 'lodash';
import { parseInclude, parseWhere } from './utils'; import { parseInclude, parseWhere } from './utils';
import { notFound } from 'boom'; import { notFound } from 'boom';
import * as associations from './associations/index';
const createAll = ({ server, model, prefix, config }) => { const createAll = ({server, model, prefix, config}) => {
Object.keys(methods).forEach((method) => { Object.keys(methods).forEach((method) => {
methods[method]({ server, model, prefix, config }); methods[method]({server, model, prefix, config});
}); });
}; };
export { associations };
/* /*
The `models` option, becomes `permissions`, and can look like: The `models` option, becomes `permissions`, and can look like:
@ -31,42 +28,42 @@ models: {
*/ */
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;
if (!permissions) { if (!permissions) {
createAll({ server, model, prefix, config }); createAll({server, model, prefix, config});
} else if (!Array.isArray(permissions)) { }
throw new Error('hapi-sequelize-crud: `models` property must be an array'); else if (Array.isArray(permissions) && permissions.includes(modelName)) {
} else if (permissions.includes(modelName)) { createAll({server, model, prefix, config});
createAll({ server, model, prefix, config }); }
} else { else if (_.isPlainObject(permissions)) {
const permissionOptions = permissions.filter((permission) => { const permittedModels = Object.keys(permissions);
return permission.model === modelName;
});
permissionOptions.forEach((permissionOption) => { if (permissions[modelName] === true) {
if (_.isPlainObject(permissionOption)) { createAll({server, model, prefix, config});
const permissionConfig = permissionOption.config || config; }
else if (permittedModels.includes(modelName)) {
if (permissionOption.methods) { if (Array.isArray(permissions[modelName])) {
permissionOption.methods.forEach((method) => { permissions[modelName].forEach((method) => {
methods[method]({ methods[method]({server, model, prefix, config});
server, });
model,
prefix,
config: permissionConfig,
});
});
} else {
createAll({ server, model, prefix, config: permissionConfig });
}
} }
}); else if (_.isPlainObject(permissions[modelName])) {
permissions[modelName].methods.forEach((method) => {
methods[method]({
server,
model,
prefix,
config: permissions[modelName].config || config,
});
});
}
}
} }
}; };
export const list = ({ server, model, prefix, config }) => { export const list = ({server, model, prefix, config}) => {
server.route({ server.route({
method: 'GET', method: 'GET',
path: `${prefix}/${model._plural}`, path: `${prefix}/${model._plural}`,
@ -89,7 +86,7 @@ export const list = ({ server, model, prefix, config }) => {
}); });
}; };
export const get = ({ server, model, prefix, config }) => { export const get = ({server, model, prefix, config}) => {
server.route({ server.route({
method: 'GET', method: 'GET',
path: `${prefix}/${model._singular}/{id?}`, path: `${prefix}/${model._singular}/{id?}`,
@ -117,7 +114,7 @@ export const get = ({ server, model, prefix, config }) => {
}); });
}; };
export const scope = ({ server, model, prefix, config }) => { export const scope = ({server, model, prefix, config}) => {
const scopes = Object.keys(model.options.scopes); const scopes = Object.keys(model.options.scopes);
server.route({ server.route({
@ -143,7 +140,7 @@ export const scope = ({ server, model, prefix, config }) => {
}); });
}; };
export const create = ({ server, model, prefix, config }) => { export const create = ({server, model, prefix, config}) => {
server.route({ server.route({
method: 'POST', method: 'POST',
path: `${prefix}/${model._singular}`, path: `${prefix}/${model._singular}`,
@ -159,7 +156,7 @@ export const create = ({ server, model, prefix, config }) => {
}); });
}; };
export const destroy = ({ server, model, prefix, config }) => { export const destroy = ({server, model, prefix, config}) => {
server.route({ server.route({
method: 'DELETE', method: 'DELETE',
path: `${prefix}/${model._singular}/{id?}`, path: `${prefix}/${model._singular}/{id?}`,
@ -180,7 +177,7 @@ export const destroy = ({ server, model, prefix, config }) => {
}); });
}; };
export const destroyAll = ({ server, model, prefix, config }) => { export const destroyAll = ({server, model, prefix, config}) => {
server.route({ server.route({
method: 'DELETE', method: 'DELETE',
path: `${prefix}/${model._plural}`, path: `${prefix}/${model._plural}`,
@ -200,7 +197,7 @@ export const destroyAll = ({ server, model, prefix, config }) => {
}); });
}; };
export const destroyScope = ({ server, model, prefix, config }) => { export const destroyScope = ({server, model, prefix, config}) => {
const scopes = Object.keys(model.options.scopes); const scopes = Object.keys(model.options.scopes);
server.route({ server.route({
@ -228,7 +225,7 @@ export const destroyScope = ({ server, model, prefix, config }) => {
}); });
}; };
export const update = ({ server, model, prefix, config }) => { export const update = ({server, model, prefix, config}) => {
server.route({ server.route({
method: 'PUT', method: 'PUT',
path: `${prefix}/${model._singular}/{id}`, path: `${prefix}/${model._singular}/{id}`,
@ -257,6 +254,9 @@ export const update = ({ server, model, prefix, config }) => {
}); });
}; };
import * as associations from './associations/index';
export { associations };
const methods = { const methods = {
list, get, scope, create, destroy, destroyAll, destroyScope, update, list, get, scope, create, destroy, destroyAll, destroyScope, update,
}; };

View File

@ -1,5 +1,3 @@
import Boom from 'boom';
export default (target, key, descriptor) => { export default (target, key, descriptor) => {
const fn = descriptor.value; const fn = descriptor.value;
@ -7,59 +5,8 @@ export default (target, key, descriptor) => {
try { try {
await fn(request, reply); await fn(request, reply);
} catch (e) { } catch (e) {
if (e.original) { console.error(e);
const { code, detail, hint } = e.original; reply(e);
let error;
// pg error codes https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html
if (code && (code.startsWith('22') || code.startsWith('23'))) {
error = Boom.wrap(e, 406);
} else if (code && (code.startsWith('42'))) {
error = Boom.wrap(e, 422);
// TODO: we could get better at parse postgres error codes
} else {
// use a 502 error code since the issue is upstream with postgres, not
// this server
error = Boom.wrap(e, 502);
}
// detail tends to be more specific information. So, if we have it, use.
if (detail) {
error.message += `: ${detail}`;
error.reformat();
}
// hint might provide useful information about how to fix the problem
if (hint) {
error.message += ` Hint: ${hint}`;
error.reformat();
}
reply(error);
} else if (!e.isBoom) {
const { message } = e;
let err;
if (e.name === 'SequelizeValidationError')
err = Boom.badData(message);
else if (e.name === 'SequelizeConnectionTimedOutError')
err = Boom.gatewayTimeout(message);
else if (e.name === 'SequelizeHostNotReachableError')
err = Boom.serverUnavailable(message);
else if (e.name === 'SequelizeUniqueConstraintError')
err = Boom.conflict(message);
else if (e.name === 'SequelizeForeignKeyConstraintError')
err = Boom.expectationFailed(message);
else if (e.name === 'SequelizeExclusionConstraintError')
err = Boom.expectationFailed(message);
else if (e.name === 'SequelizeConnectionError')
err = Boom.badGateway(message);
else err = Boom.badImplementation(message);
reply(err);
} else {
reply(e);
}
} }
}; };

View File

@ -1,5 +1,5 @@
if (!global._babelPolyfill) { if (!global._babelPolyfill) {
require('babel-polyfill'); require('babel/polyfill');
} }
import crud, { associations } from './crud'; import crud, { associations } from './crud';