feat(simple): simple CRUD REST API (no associations)
feat(associations): one-to-one associations feat(associations): one-to-many associations
This commit is contained in:
commit
bae6820e64
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
#### joe made this: https://goel.io/joe
|
||||
|
||||
#####=== Node ===#####
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
# Debug log from npm
|
||||
npm-debug.log
|
||||
|
29
Gruntfile.js
Normal file
29
Gruntfile.js
Normal 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']);
|
||||
};
|
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "hapi-sequelize-crud",
|
||||
"version": "1.0.0",
|
||||
"description": "Hapi plugin that automatically generates RESTful API for CRUD",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"git": "https://github.com/mdibaiee/hapi-sequelize-crud"
|
||||
},
|
||||
"author": "Mahdi Dibaiee <mdibaiee@aol.com> (http://dibaiee.ir/)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel": "5.8.3",
|
||||
"ghooks": "1.0.3",
|
||||
"grunt": "0.4.5",
|
||||
"grunt-babel": "5.0.3",
|
||||
"grunt-contrib-clean": "0.7.0",
|
||||
"grunt-contrib-watch": "0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"joi": "7.2.1"
|
||||
}
|
||||
}
|
4
src/associations/index.js
Normal file
4
src/associations/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import oneToOne from './one-to-one';
|
||||
import oneToMany from './one-to-many';
|
||||
|
||||
export { oneToOne, oneToMany };
|
0
src/associations/many-to-many.js
Normal file
0
src/associations/many-to-many.js
Normal file
73
src/associations/one-to-many.js
Normal file
73
src/associations/one-to-many.js
Normal file
@ -0,0 +1,73 @@
|
||||
import joi from 'joi';
|
||||
import error from '../error';
|
||||
|
||||
let prefix;
|
||||
|
||||
export default (server, a, b, options) => {
|
||||
prefix = options.prefix;
|
||||
|
||||
list(server, a, b);
|
||||
destroy(server, a, b);
|
||||
update(server, a, b);
|
||||
}
|
||||
|
||||
export const list = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${prefix}/${a._singular}/{aid}/${b._plural}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let list = await request.models[b.name].findAll({
|
||||
where: {
|
||||
...request.query,
|
||||
[a.name + 'Id']: request.params.aid
|
||||
}
|
||||
});
|
||||
|
||||
reply(list);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const destroy = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'DELETE',
|
||||
path: `${prefix}/${a._singular}/{aid}/${b._plural}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let list = await request.models[b.name].findAll({
|
||||
where: {
|
||||
...request.query,
|
||||
[a.name + 'Id']: request.params.aid
|
||||
}
|
||||
});
|
||||
|
||||
await* list.map(instance => instance.destroy());
|
||||
|
||||
reply();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const update = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'PUT',
|
||||
path: `${prefix}/${a._singular}/{aid}/${b._plural}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let list = await request.models[b.name].findOne({
|
||||
where: {
|
||||
...request.query,
|
||||
[a.name + 'Id']: request.params.aid
|
||||
}
|
||||
});
|
||||
|
||||
await* list.map(instance => instance.update(request.payload));
|
||||
|
||||
reply(list);
|
||||
}
|
||||
})
|
||||
}
|
89
src/associations/one-to-one.js
Normal file
89
src/associations/one-to-one.js
Normal file
@ -0,0 +1,89 @@
|
||||
import joi from 'joi';
|
||||
import error from '../error';
|
||||
|
||||
let prefix;
|
||||
|
||||
export default (server, a, b, options) => {
|
||||
prefix = options.prefix;
|
||||
|
||||
get(server, a, b);
|
||||
create(server, a, b);
|
||||
destroy(server, a, b);
|
||||
update(server, a, b);
|
||||
}
|
||||
|
||||
export const get = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${prefix}/${a._singular}/{aid}/${b._singular}/{bid}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let instance = await request.models[b.name].findOne({
|
||||
where: {
|
||||
id: request.params.bid,
|
||||
[a.name + 'Id']: request.params.aid
|
||||
}
|
||||
});
|
||||
|
||||
reply(instance);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const create = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: `${prefix}/${a._singular}/{id}/${b._singular}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
request.payload[a.name + 'Id'] = request.params.id;
|
||||
let instance = await request.models[b.name].create(request.payload);
|
||||
|
||||
reply(instance);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const destroy = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'DELETE',
|
||||
path: `${prefix}/${a._singular}/{aid}/${b._singular}/{bid}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let instance = await request.models[b.name].findOne({
|
||||
where: {
|
||||
id: request.params.bid,
|
||||
[a.name + 'Id']: request.params.aid
|
||||
}
|
||||
});
|
||||
|
||||
await instance.destroy();
|
||||
|
||||
reply();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const update = (server, a, b) => {
|
||||
server.route({
|
||||
method: 'PUT',
|
||||
path: `${prefix}/${a._singular}/{aid}/${b._singular}/{bid}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let instance = await request.models[b.name].findOne({
|
||||
where: {
|
||||
id: request.params.bid,
|
||||
[a.name + 'Id']: request.params.aid
|
||||
}
|
||||
});
|
||||
|
||||
await instance.update(request.payload);
|
||||
|
||||
reply(instance);
|
||||
}
|
||||
})
|
||||
}
|
133
src/crud.js
Normal file
133
src/crud.js
Normal file
@ -0,0 +1,133 @@
|
||||
import joi from 'joi';
|
||||
import error from './error';
|
||||
|
||||
let prefix;
|
||||
|
||||
export default (server, model, options) => {
|
||||
prefix = options.prefix;
|
||||
|
||||
list(server, model);
|
||||
get(server, model);
|
||||
scope(server, model);
|
||||
create(server, model);
|
||||
destroy(server, model);
|
||||
update(server, model);
|
||||
}
|
||||
|
||||
export const list = (server, model) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${prefix}/${model._plural}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
console.log(request.models[model.name], request.query);
|
||||
let list = await request.models[model.name].findAll({
|
||||
where: request.query
|
||||
});
|
||||
|
||||
reply(list);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const get = (server, model) => {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${prefix}/${model._singular}/{id?}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let where = request.params.id ? { id : request.params.id } : request.query;
|
||||
|
||||
let instance = await request.models[model.name].findOne({ where });
|
||||
|
||||
reply(instance);
|
||||
},
|
||||
config: {
|
||||
validate: {
|
||||
params: joi.object().keys({
|
||||
id: joi.number().integer()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const scope = (server, model) => {
|
||||
let scopes = Object.keys(model.options.scopes);
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `${prefix}/${model._plural}/{scope}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let list = await request.models[model.name].scope(request.params.scope).findAll();
|
||||
|
||||
reply(list);
|
||||
},
|
||||
config: {
|
||||
validate: {
|
||||
params: joi.object().keys({
|
||||
scope: joi.string().valid(...scopes)
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const create = (server, model) => {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: `${prefix}/${model._singular}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let instance = await request.models[model.name].create(request.payload);
|
||||
|
||||
reply(instance);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const destroy = (server, model) => {
|
||||
server.route({
|
||||
method: 'DELETE',
|
||||
path: `${prefix}/${model._singular}/{id?}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let where = request.params.id ? { id : request.params.id } : request.query;
|
||||
|
||||
let list = await request.models[model.name].findAll({ where });
|
||||
|
||||
await* list.map(instance => instance.destroy());
|
||||
|
||||
reply();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const update = (server, model) => {
|
||||
server.route({
|
||||
method: 'PUT',
|
||||
path: `/v1/${model._singular}/{id}`,
|
||||
|
||||
@error
|
||||
async handler(request, reply) {
|
||||
let instance = await request.models[model.name].findOne({
|
||||
where: {
|
||||
id: request.params.id
|
||||
}
|
||||
});
|
||||
|
||||
await instance.update(request.payload);
|
||||
|
||||
reply(instance);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
import * as associations from './associations/index';
|
||||
export { associations };
|
13
src/error.js
Normal file
13
src/error.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default (target, key, descriptor) => {
|
||||
let fn = descriptor.value;
|
||||
|
||||
descriptor.value = (request, reply) => {
|
||||
try {
|
||||
fn(request, reply);
|
||||
} catch(e) {
|
||||
reply(e);
|
||||
}
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
60
src/index.js
Normal file
60
src/index.js
Normal file
@ -0,0 +1,60 @@
|
||||
import crud, { associations } from './crud';
|
||||
|
||||
const register = (server, options = {}, next) => {
|
||||
options.prefix = options.prefix || '';
|
||||
|
||||
let db = server.plugins['hapi-sequelize'].db;
|
||||
let models = db.sequelize.models;
|
||||
|
||||
for (let modelName of Object.keys(models)) {
|
||||
let model = models[modelName];
|
||||
let { plural, singular } = model.options.name;
|
||||
model._plural = plural.toLowerCase();
|
||||
model._singular = singular.toLowerCase();
|
||||
|
||||
// Join tables
|
||||
if (model.options.name.singular !== model.name) continue;
|
||||
|
||||
crud(server, model, options);
|
||||
|
||||
for (let key of Object.keys(model.associations)) {
|
||||
let association = model.associations[key];
|
||||
let { associationType, source, target } = association;
|
||||
|
||||
let sourceName = source.options.name;
|
||||
let targetName = target.options.name;
|
||||
|
||||
target._plural = targetName.plural.toLowerCase();
|
||||
target._singular = targetName.singular.toLowerCase();
|
||||
|
||||
let targetAssociations = target.associations[sourceName.plural] || target.associations[sourceName.singular];
|
||||
let sourceType = association.associationType,
|
||||
targetType = (targetAssociations || {}).associationType;
|
||||
|
||||
try {
|
||||
if (sourceType === 'BelongsTo' && (targetType === 'BelongsTo' || !targetType)) {
|
||||
associations.oneToOne(server, source, target, options);
|
||||
associations.oneToOne(server, target, source, options);
|
||||
}
|
||||
|
||||
if (sourceType === 'BelongsTo' && (targetType === 'HasMany')) {
|
||||
associations.oneToOne(server, source, target, options);
|
||||
associations.oneToOne(server, target, source, options);
|
||||
associations.oneToMany(server, target, source, options);
|
||||
}
|
||||
} catch(e) {
|
||||
// There might be conflicts in case of models associated with themselves and some other
|
||||
// rare cases.
|
||||
}
|
||||
console.log(sourceName.singular, sourceType, targetName.singular, ' & ', targetName.singular, targetType, sourceName.singular);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
register.attributes = {
|
||||
pkg: require('../package.json')
|
||||
}
|
||||
|
||||
export { register };
|
Loading…
Reference in New Issue
Block a user