5 Commits

Author SHA1 Message Date
5f0273a973 2.2.0 2016-07-05 19:17:26 -07:00
3317e0a8f2 Fork to @getable 2016-07-05 19:17:06 -07:00
61fbf434af Merge branch 'require-payload-on-put' 2016-07-05 19:12:45 -07:00
f71e06362b Merge branch 'fix-include' 2016-07-05 19:12:34 -07:00
a5e6b2dd46 Fix: include option api correction
AFAIK, hapi-sequelize doesn't have a `request.models`, but it does have
a `request.getDb()` method that has `models` on it. This calls that
method to get the related models, but allows `request.models` to keep
working for backward compatibility.
2016-07-05 18:33:03 -07:00
10 changed files with 119 additions and 175 deletions

29
Gruntfile.js Normal file
View File

@ -0,0 +1,29 @@
module.exports = function(grunt) {
grunt.initConfig({
babel: {
scripts: {
files: [{
expand: true,
cwd: 'src',
src: '**/*.js',
dest: 'build/'
}]
}
},
clean: {
files: ['build/**/*.js']
},
watch: {
scripts: {
files: ['src/**/*.js', 'server/**/*.js'],
tasks: ['babel']
}
}
});
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['clean', 'babel']);
};

View File

@ -28,42 +28,11 @@ await register({
register: require('hapi-sequelize-crud'), register: require('hapi-sequelize-crud'),
options: { options: {
prefix: '/v1', prefix: '/v1',
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
models: ['cat', 'dog'] // only the cat and dog models will have routes created
// or
models: [
// possible methods: list, get, scope, create, destroy, destroyAll, destroyScope, update
// the cat model only has get and list methods enabled
{model: 'cat', methods: ['get', 'list']},
// the dog model has all methods enabled
{model: 'dog'},
// the cow model also has all methods enabled
'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']}
]
models: {
bat: {
}
}
} }
}); });
``` ```
### Methods
* list: get all rows in a table
* get: get a single row
* scope: reference a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model)
* create: create a new row
* destroy: delete a row
* destroyAll: delete all models in the table
* destroyScope: use a [sequelize scope](http://docs.sequelizejs.com/en/latest/api/model/#scopeoptions-model) to find rows, then delete them
* update: update a row
Please note that you should register `hapi-sequelize-crud` after defining your Please note that you should register `hapi-sequelize-crud` after defining your
associations. associations.

View File

@ -1,6 +1,6 @@
{ {
"name": "@getable/hapi-sequelize-crud", "name": "@getable/hapi-sequelize-crud",
"version": "2.5.1", "version": "2.2.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": {
@ -30,7 +30,7 @@
"babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3",
"babel-preset-stage-1": "^6.5.0", "babel-preset-stage-1": "^6.5.0",
"eslint": "2.10.2", "eslint": "2.10.2",
"eslint-config-pichak": "1.1.0", "eslint-config-pichak": "1.0.1",
"ghooks": "1.0.3", "ghooks": "1.0.3",
"scripty": "^1.6.0" "scripty": "^1.6.0"
}, },

View File

@ -14,15 +14,15 @@ export default (server, a, b, names, options) => {
@error @error
async handler(request, reply) { async handler(request, reply) {
const instanceb = await b.findOne({ let instanceb = await b.findOne({
where: { where: {
[b.primaryKeyField]: request.params.bid, id: request.params.bid,
}, },
}); });
const instancea = await a.findOne({ let instancea = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });

View File

@ -28,23 +28,18 @@ export const get = (server, a, b, names) => {
async handler(request, reply) { async handler(request, reply) {
const include = parseInclude(request); const include = parseInclude(request);
const base = await a.findOne({ const base = a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
const method = getMethod(base, names.b); const method = getMethod(base, names.b);
const list = await method({ where: { const list = await method({ where: {
[b.primaryKeyField]: request.params.bid, id: request.params.bid,
}, include }); }, include });
if (Array.isArray(list)) { reply(list);
reply(list[0]);
} else {
reply(list);
}
}, },
config: defaultConfig, config: defaultConfig,
@ -63,7 +58,7 @@ export const list = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
@ -78,7 +73,7 @@ export const list = (server, a, b, names) => {
}; };
export const scope = (server, a, b, names) => { export const scope = (server, a, b, names) => {
const scopes = Object.keys(b.options.scopes); let scopes = Object.keys(b.options.scopes);
server.route({ server.route({
method: 'GET', method: 'GET',
@ -91,7 +86,7 @@ export const scope = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
@ -117,7 +112,7 @@ export const scope = (server, a, b, names) => {
}; };
export const scopeScope = (server, a, b, names) => { export const scopeScope = (server, a, b, names) => {
const scopes = { let scopes = {
a: Object.keys(a.options.scopes), a: Object.keys(a.options.scopes),
b: Object.keys(b.options.scopes), b: Object.keys(b.options.scopes),
}; };
@ -131,7 +126,7 @@ export const scopeScope = (server, a, b, names) => {
const include = parseInclude(request); const include = parseInclude(request);
const where = parseWhere(request); const where = parseWhere(request);
const list = await b.scope(request.params.scopeb).findAll({ let list = await b.scope(request.params.scopeb).findAll({
where, where,
include: include.concat({ include: include.concat({
model: a.scope(request.params.scopea), model: a.scope(request.params.scopea),
@ -164,7 +159,7 @@ export const destroy = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
@ -180,7 +175,7 @@ export const destroy = (server, a, b, names) => {
}; };
export const destroyScope = (server, a, b, names) => { export const destroyScope = (server, a, b, names) => {
const scopes = Object.keys(b.options.scopes); let scopes = Object.keys(b.options.scopes);
server.route({ server.route({
method: 'DELETE', method: 'DELETE',
@ -193,7 +188,7 @@ export const destroyScope = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primarykeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
@ -233,7 +228,7 @@ export const update = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });

View File

@ -26,7 +26,7 @@ export const get = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
const method = getMethod(base, names.b, false); const method = getMethod(base, names.b, false);
@ -53,7 +53,7 @@ export const create = (server, a, b, names) => {
async handler(request, reply) { async handler(request, reply) {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.id, id: request.params.id,
}, },
}); });
@ -79,12 +79,10 @@ export const destroy = (server, a, b, names) => {
const base = await a.findOne({ const base = await a.findOne({
where: { where: {
[a.primaryKeyField]: request.params.aid, id: request.params.aid,
}, },
}); });
where[b.primaryKeyField] = request.params.bid;
const method = getMethod(base, names.b, false, 'get'); const method = getMethod(base, names.b, false, 'get');
const instance = await method({ where, include }); const instance = await method({ where, include });
await instance.destroy(); await instance.destroy();
@ -112,8 +110,6 @@ export const update = (server, a, b, names) => {
}, },
}); });
where[b.primaryKeyField] = request.params.bid;
const method = getMethod(base, names.b, false); const method = getMethod(base, names.b, false);
const instance = await method({ where, include }); const instance = await method({ where, include });

View File

@ -4,65 +4,24 @@ import _ from 'lodash';
import { parseInclude, parseWhere } from './utils'; import { parseInclude, parseWhere } from './utils';
import { notFound } from 'boom'; import { notFound } from 'boom';
const createAll = ({ server, model, prefix, config }) => { let prefix;
Object.keys(methods).forEach((method) => { let defaultConfig;
methods[method]({ server, model, prefix, config });
}); export default (server, model, options) => {
prefix = options.prefix;
defaultConfig = options.defaultConfig;
list(server, model);
get(server, model);
scope(server, model);
create(server, model);
destroy(server, model);
destroyAll(server, model);
destroyScope(server, model);
update(server, model);
}; };
/* export const list = (server, model) => {
The `models` option, becomes `permissions`, and can look like:
```
models: ['cat', 'dog']
```
or
```
models: {
cat: ['list', 'get']
, dog: true // all
}
```
*/
export default (server, model, { prefix, defaultConfig: config, models: permissions }) => {
const modelName = model._singular;
const permissionsIsArray = Array.isArray(permissions);
if (!permissions) {
createAll({ server, model, prefix, config });
} else if (permissionsIsArray && permissions.includes(modelName)) {
createAll({ server, model, prefix, config });
} else if (permissionsIsArray) {
const permissionOptions = permissions.filter((permission) => {
return permission.model === modelName;
});
permissionOptions.forEach((permissionOption) => {
if (_.isPlainObject(permissionOption)) {
const permissionConfig = permissionOption.config || config;
if (permissionOption.methods) {
permissionOption.methods.forEach((method) => {
methods[method]({
server,
model,
prefix,
config: permissionConfig,
});
});
} else {
createAll({ server, model, prefix, config: permissionConfig });
}
}
});
}
};
export const list = ({ server, model, prefix, config }) => {
server.route({ server.route({
method: 'GET', method: 'GET',
path: `${prefix}/${model._plural}`, path: `${prefix}/${model._plural}`,
@ -81,11 +40,11 @@ export const list = ({ server, model, prefix, config }) => {
reply(list); reply(list);
}, },
config, config: defaultConfig,
}); });
}; };
export const get = ({ server, model, prefix, config }) => { export const get = (server, model) => {
server.route({ server.route({
method: 'GET', method: 'GET',
path: `${prefix}/${model._singular}/{id?}`, path: `${prefix}/${model._singular}/{id?}`,
@ -94,8 +53,8 @@ export const get = ({ server, model, prefix, config }) => {
async handler(request, reply) { async handler(request, reply) {
const include = parseInclude(request); const include = parseInclude(request);
const where = parseWhere(request); const where = parseWhere(request);
const { id } = request.params; const {id} = request.params;
if (id) where[model.primaryKeyField] = id; if (id) where.id = id;
const instance = await model.findOne({ where, include }); const instance = await model.findOne({ where, include });
@ -109,12 +68,12 @@ export const get = ({ server, model, prefix, config }) => {
id: joi.any(), id: joi.any(),
}), }),
}, },
}, config), }, defaultConfig),
}); });
}; };
export const scope = ({ server, model, prefix, config }) => { export const scope = (server, model) => {
const scopes = Object.keys(model.options.scopes); let scopes = Object.keys(model.options.scopes);
server.route({ server.route({
method: 'GET', method: 'GET',
@ -135,11 +94,11 @@ export const scope = ({ server, model, prefix, config }) => {
scope: joi.string().valid(...scopes), scope: joi.string().valid(...scopes),
}), }),
}, },
}, config), }, defaultConfig),
}); });
}; };
export const create = ({ server, model, prefix, config }) => { export const create = (server, model) => {
server.route({ server.route({
method: 'POST', method: 'POST',
path: `${prefix}/${model._singular}`, path: `${prefix}/${model._singular}`,
@ -151,11 +110,11 @@ export const create = ({ server, model, prefix, config }) => {
reply(instance); reply(instance);
}, },
config, config: defaultConfig,
}); });
}; };
export const destroy = ({ server, model, prefix, config }) => { export const destroy = (server, model) => {
server.route({ server.route({
method: 'DELETE', method: 'DELETE',
path: `${prefix}/${model._singular}/{id?}`, path: `${prefix}/${model._singular}/{id?}`,
@ -163,7 +122,7 @@ export const destroy = ({ server, model, prefix, config }) => {
@error @error
async handler(request, reply) { async handler(request, reply) {
const where = parseWhere(request); const where = parseWhere(request);
if (request.params.id) where[model.primaryKeyField] = request.params.id; if (request.params.id) where.id = request.params.id;
const list = await model.findAll({ where }); const list = await model.findAll({ where });
@ -172,11 +131,11 @@ export const destroy = ({ server, model, prefix, config }) => {
reply(list.length === 1 ? list[0] : list); reply(list.length === 1 ? list[0] : list);
}, },
config, config: defaultConfig,
}); });
}; };
export const destroyAll = ({ server, model, prefix, config }) => { export const destroyAll = (server, model) => {
server.route({ server.route({
method: 'DELETE', method: 'DELETE',
path: `${prefix}/${model._plural}`, path: `${prefix}/${model._plural}`,
@ -192,12 +151,12 @@ export const destroyAll = ({ server, model, prefix, config }) => {
reply(list.length === 1 ? list[0] : list); reply(list.length === 1 ? list[0] : list);
}, },
config, config: defaultConfig,
}); });
}; };
export const destroyScope = ({ server, model, prefix, config }) => { export const destroyScope = (server, model) => {
const scopes = Object.keys(model.options.scopes); let scopes = Object.keys(model.options.scopes);
server.route({ server.route({
method: 'DELETE', method: 'DELETE',
@ -208,7 +167,7 @@ export const destroyScope = ({ server, model, prefix, config }) => {
const include = parseInclude(request); const include = parseInclude(request);
const where = parseWhere(request); const where = parseWhere(request);
const list = await model.scope(request.params.scope).findAll({ include, where }); let list = await model.scope(request.params.scope).findAll({ include, where });
await Promise.all(list.map(instance => instance.destroy())); await Promise.all(list.map(instance => instance.destroy()));
@ -220,18 +179,18 @@ export const destroyScope = ({ server, model, prefix, config }) => {
scope: joi.string().valid(...scopes), scope: joi.string().valid(...scopes),
}), }),
}, },
}, config), }, defaultConfig),
}); });
}; };
export const update = ({ server, model, prefix, config }) => { export const update = (server, model) => {
server.route({ server.route({
method: 'PUT', method: 'PUT',
path: `${prefix}/${model._singular}/{id}`, path: `${prefix}/${model._singular}/{id}`,
@error @error
async handler(request, reply) { async handler(request, reply) {
const { id } = request.params; const {id} = request.params;
const instance = await model.findOne({ const instance = await model.findOne({
where: { where: {
id, id,
@ -249,13 +208,9 @@ export const update = ({ server, model, prefix, config }) => {
validate: { validate: {
payload: joi.object().required(), payload: joi.object().required(),
}, },
}, config), }, defaultConfig),
}); });
}; };
import * as associations from './associations/index'; import * as associations from './associations/index';
export { associations }; export { associations };
const methods = {
list, get, scope, create, destroy, destroyAll, destroyScope, update,
};

View File

@ -1,10 +1,10 @@
export default (target, key, descriptor) => { export default (target, key, descriptor) => {
const fn = descriptor.value; let fn = descriptor.value;
descriptor.value = async (request, reply) => { descriptor.value = async (request, reply) => {
try { try {
await fn(request, reply); await fn(request, reply);
} catch (e) { } catch(e) {
console.error(e); console.error(e);
reply(e); reply(e);
} }

View File

@ -8,10 +8,9 @@ import qs from 'qs';
const register = (server, options = {}, next) => { const register = (server, options = {}, next) => {
options.prefix = options.prefix || ''; options.prefix = options.prefix || '';
options.name = options.name || 'db';
const db = server.plugins['hapi-sequelize'][options.name]; let db = server.plugins['hapi-sequelize'].db;
const models = db.sequelize.models; let models = db.sequelize.models;
const onRequest = function (request, reply) { const onRequest = function (request, reply) {
const uri = request.raw.req.url; const uri = request.raw.req.url;
@ -27,9 +26,9 @@ const register = (server, options = {}, next) => {
method: onRequest, method: onRequest,
}); });
for (const modelName of Object.keys(models)) { for (let modelName of Object.keys(models)) {
const model = models[modelName]; let model = models[modelName];
const { plural, singular } = model.options.name; let { plural, singular } = model.options.name;
model._plural = plural.toLowerCase(); model._plural = plural.toLowerCase();
model._singular = singular.toLowerCase(); model._singular = singular.toLowerCase();
@ -38,30 +37,29 @@ const register = (server, options = {}, next) => {
crud(server, model, options); crud(server, model, options);
for (const key of Object.keys(model.associations)) { for (let key of Object.keys(model.associations)) {
const association = model.associations[key]; let association = model.associations[key];
const { source, target } = association; let { source, target } = association;
const sourceName = source.options.name; let sourceName = source.options.name;
const names = (rev) => { const names = (rev) => {
const arr = [{ const arr = [{
plural: sourceName.plural.toLowerCase(), plural: sourceName.plural.toLowerCase(),
singular: sourceName.singular.toLowerCase(), singular: sourceName.singular.toLowerCase(),
original: sourceName, original: sourceName,
}, { }, {
plural: association.options.name.plural.toLowerCase(), plural: association.options.name.plural.toLowerCase(),
singular: association.options.name.singular.toLowerCase(), singular: association.options.name.singular.toLowerCase(),
original: association.options.name, original: association.options.name,
}]; }];
return rev ? { b: arr[0], a: arr[1] } : { a: arr[0], b: arr[1] }; return rev ? { b: arr[0], a: arr[1] } : { a: arr[0], b: arr[1] };
}; };
const targetAssociations = target.associations[sourceName.plural] let targetAssociations = target.associations[sourceName.plural] || target.associations[sourceName.singular];
|| target.associations[sourceName.singular]; let sourceType = association.associationType,
const sourceType = association.associationType, targetType = (targetAssociations || {}).associationType;
targetType = (targetAssociations || {}).associationType;
try { try {
if (sourceType === 'BelongsTo' && (targetType === 'BelongsTo' || !targetType)) { if (sourceType === 'BelongsTo' && (targetType === 'BelongsTo' || !targetType)) {
@ -85,7 +83,7 @@ const register = (server, options = {}, next) => {
associations.associate(server, source, target, names(), options); associations.associate(server, source, target, names(), options);
associations.associate(server, target, source, names(1), options); associations.associate(server, target, source, names(1), options);
} catch (e) { } catch(e) {
// There might be conflicts in case of models associated with themselves and some other // There might be conflicts in case of models associated with themselves and some other
// rare cases. // rare cases.
} }

View File

@ -5,14 +5,16 @@ export const parseInclude = request => {
: [request.query.include]; : [request.query.include];
const noGetDb = typeof request.getDb !== 'function'; const noGetDb = typeof request.getDb !== 'function';
const noRequestModels = !request.models; const noRequestModels = request.models;
if (noGetDb && noRequestModels) { if (noGetDb && noRequestModels) {
return new Error('`request.getDb` or `request.models` are not defined.' return new Error('`request.getDb` or `request.models` are not defined. Be sure to load hapi-sequelize before hapi-sequelize-crud.');
+ 'Be sure to load hapi-sequelize before hapi-sequelize-crud.');
} }
const { models } = noGetDb ? request : request.getDb(); const {models} = !noGetDb
? request.getDb()
: request
;
return include.map(a => { return include.map(a => {
if (typeof a === 'string') return models[a]; if (typeof a === 'string') return models[a];