From 7c8b97bd13bb97f755e7601b3a7b26950d873e10 Mon Sep 17 00:00:00 2001 From: oof1lab Date: Tue, 31 Oct 2017 12:29:11 +0000 Subject: [PATCH] feature: creating actual routes for each table instead of mapping from request params version : 0.0.4 --- README.md | 18 ++--- lib/util/cmd.helper.js | 2 +- lib/xapi.js | 163 ++++++++++++++++++++++++++++++----------- lib/xsql.js | 27 ++++--- package.json | 2 +- 5 files changed, 146 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index fa70ac1d36..20050e1c64 100644 --- a/README.md +++ b/README.md @@ -59,18 +59,18 @@ Root URL (localhost:3000/) returns all REST API urls for each table in schema. ## CRUD APIs Usual Suspects -* GET       /api/:tableName -* POST      /api/:tableName -* GET       /api/:tableName/:id -* PUT       /api/:tableName/:id -* GET       /api/:tableName/count -* GET       /api/:tableName/exists -* GET       /api/:parentTable/:id/:childTable -* DELETE  /api/:tableName/:id +* GET       /api/tableName +* POST      /api/tableName +* GET       /api/tableName/:id +* PUT       /api/tableName/:id +* GET       /api/tableName/count +* GET       /api/tableName/exists +* GET       /api/parentTable/:id/childTable +* DELETE  /api/tableName/:id * POST     /dynamic ## Other APIS -* GET      /api/:tableName/describe +* GET      /api/tableName/describe * GET      /api/tables ## Support for composite primary keys diff --git a/lib/util/cmd.helper.js b/lib/util/cmd.helper.js index ca3ac8efb8..3b7c97122a 100644 --- a/lib/util/cmd.helper.js +++ b/lib/util/cmd.helper.js @@ -11,7 +11,7 @@ program.on('--help', () => { }) program - .version('0.0.3') + .version('0.0.4') .option('-h, --host ', 'hostname') .option('-d, --database ', 'database schema name') .option('-u, --user ', 'username of database / root by default') diff --git a/lib/xapi.js b/lib/xapi.js index 76f023353c..c57b130f99 100644 --- a/lib/xapi.js +++ b/lib/xapi.js @@ -18,6 +18,7 @@ class Xapi { this.mysql.init((err, results) => { + this.app.use(this.urlMiddleware) this.setupRoutes() this.app.use(this.errorMiddleware) cbk(err, results) @@ -26,9 +27,27 @@ class Xapi { } + + urlMiddleware(req, res, next) { + + let justUrl = req.originalUrl.split('?')[0] + let pathSplit = justUrl.split('/') + + if (pathSplit.length >= 2 && pathSplit[1] === 'api') { + if (pathSplit.length >= 5) { + req.app.locals._parentTable = pathSplit[2] + req.app.locals._childTable = pathSplit[4] + } else { + req.app.locals._tableName = pathSplit[2] + } + } + + next(); + } + errorMiddleware(err, req, res, next) { - if(err && err.code) + if (err && err.code) res.status(400).json({error: err}); else res.status(500).json({error: 'Internal server error : ' + err.message}); @@ -47,10 +66,8 @@ class Xapi { root(req, res) { - //res.sendFile(path + "index.html") let v = []; - - v = this.mysql.getSchemaRoutes(req.protocol + '://' + req.get('host') + '/api/'); + v = this.mysql.getSchemaRoutes(false, req.protocol + '://' + req.get('host') + '/api/'); v = v.concat(this.mysql.globalRoutesPrint(req.protocol + '://' + req.get('host') + '/api/')) res.json(v) @@ -59,36 +76,96 @@ class Xapi { setupRoutes() { - this.app.get('/', this.asyncMiddleware(this.root.bind(this))) - /**************** START : tables apis ****************/ + this.app.get('/', this.asyncMiddleware(this.root.bind(this))) this.app.route('/api/tables') .get(this.asyncMiddleware(this.tables.bind(this))); - this.app.route('/api/:tableName/describe') - .get(this.asyncMiddleware(this.tableDescribe.bind(this))); - /**************** END : tables apis ****************/ + let resources = []; + + resources = this.mysql.getSchemaRoutes(true, '/api/'); + + for (var j = 0; j < resources.length; ++j) { - /**************** START : basic apis ****************/ - this.app.route('/api/:tableName/count') - .get(this.asyncMiddleware(this.count.bind(this))); + let routes = resources[j]['routes']; - this.app.route('/api/:tableName') - .get(this.asyncMiddleware(this.list.bind(this))) - .post(this.asyncMiddleware(this.create.bind(this))); + for (var i = 0; i < routes.length; ++i) { - this.app.route('/api/:tableName/:id') - .get(this.asyncMiddleware(this.read.bind(this))) - .put(this.asyncMiddleware(this.update.bind(this))) - .delete(this.asyncMiddleware(this.delete.bind(this))); + switch (routes[i]['routeType']) { + + case 'list': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.list.bind(this))); + break; + + case 'create': + this.app.route(routes[i]['routeUrl']) + .post(this.asyncMiddleware(this.create.bind(this))); + break; + + case 'read': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.read.bind(this))); + break; + + case 'update': + this.app.route(routes[i]['routeUrl']) + .put(this.asyncMiddleware(this.update.bind(this))); + break; + + case 'delete': + this.app.route(routes[i]['routeUrl']) + .delete(this.asyncMiddleware(this.delete.bind(this))); + break; + + case 'exists': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.exists.bind(this))); + break; + + case 'count': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.count.bind(this))); + break; + + case 'describe': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.tableDescribe.bind(this))); + break; + + case 'relational': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.nestedList.bind(this))); + break; + + } + } + } - this.app.route('/api/:tableName/:id/exists') - .get(this.asyncMiddleware(this.exists.bind(this))); - this.app.route('/api/:parentTable/:id/:childTable') - .get(this.asyncMiddleware(this.nestedList.bind(this))); - /**************** END : basic apis ****************/ + // this.app.route('/api/:tableName/describe') + // .get(this.asyncMiddleware(this.tableDescribe.bind(this))); + // + // /**************** START : basic apis ****************/ + // this.app.route('/api/:tableName/count') + // .get(this.asyncMiddleware(this.count.bind(this))); + // + // this.app.route('/api/:tableName') + // .get(this.asyncMiddleware(this.list.bind(this))) + // .post(this.asyncMiddleware(this.create.bind(this))); + // + // this.app.route('/api/:tableName/:id') + // .get(this.asyncMiddleware(this.read.bind(this))) + // .put(this.asyncMiddleware(this.update.bind(this))) + // .delete(this.asyncMiddleware(this.delete.bind(this))); + // + // this.app.route('/api/:tableName/:id/exists') + // .get(this.asyncMiddleware(this.exists.bind(this))); + // + // this.app.route('/api/:parentTable/:id/:childTable') + // .get(this.asyncMiddleware(this.nestedList.bind(this))); + // /**************** END : basic apis ****************/ if (this.sqlConfig.dynamic === 1) { @@ -104,7 +181,7 @@ class Xapi { let query = 'INSERT INTO ?? SET ?'; let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); params.push(req.body); var results = await this.mysql.exec(query, params); @@ -114,12 +191,12 @@ class Xapi { async list(req, res) { - let cols = this.mysql.getColumnsForSelectStmt(req.params.tableName, req.query); + let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._tableName, req.query); let query = 'select ' + cols + ' from ?? '; let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); - query = query + this.mysql.getOrderByClause(req.query, req.params.tableName); + query = query + this.mysql.getOrderByClause(req.query, req.app.locals._tableName); query = query + ' limit ?,? ' params = params.concat(this.mysql.getLimitClause(req.query)); @@ -131,15 +208,15 @@ class Xapi { async nestedList(req, res) { - let cols = this.mysql.getColumnsForSelectStmt(req.params.childTable, req.query); + let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._childTable, req.query); let query = 'select ' + cols + ' from ?? where '; let params = []; - params.push(req.params.childTable); + params.push(req.app.locals._childTable); - let whereClause = this.mysql.getForeignKeyWhereClause(req.params.parentTable, + let whereClause = this.mysql.getForeignKeyWhereClause(req.app.locals._parentTable, req.params.id, - req.params.childTable); + req.app.locals._childTable); if (!whereClause) { return res.status(400).send({ @@ -149,7 +226,7 @@ class Xapi { query += whereClause; - query = query + this.mysql.getOrderByClause(req.query, req.params.parentTable); + query = query + this.mysql.getOrderByClause(req.query, req.app.locals._parentTable); query = query + ' limit ?,? ' params = params.concat(this.mysql.getLimitClause(req.query)); @@ -164,9 +241,9 @@ class Xapi { let query = 'select * from ?? where '; let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); - let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, + let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName, req.params.id.split('___')); @@ -190,9 +267,9 @@ class Xapi { let query = 'select * from ?? where '; let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); - let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, + let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName, req.params.id.split('___')); if (!clause) { @@ -225,7 +302,7 @@ class Xapi { // where clause query += updateKeys + ' where ' - let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, + let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName, req.params.id.split('___')); if (!clause) { @@ -238,7 +315,7 @@ class Xapi { // params let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); params = params.concat(Object.values(req.body)); let results = await this.mysql.exec(query, params); @@ -252,9 +329,9 @@ class Xapi { let query = 'DELETE FROM ?? WHERE '; let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); - let clause = this.mysql.getPrimaryKeyWhereClause(req.params.tableName, + let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName, req.params.id.split('___')); if (!clause) { @@ -276,7 +353,7 @@ class Xapi { let query = 'select count(1) as no_of_rows from ??'; let params = []; - params.push(req.params.tableName); + params.push(req.app.locals._tableName); let results = await this.mysql.exec(query, params); res.status(200).json(results); @@ -308,7 +385,7 @@ class Xapi { async tableDescribe(req, res) { let query = 'describe ??'; - let params = [req.params.tableName]; + let params = [req.app.locals._tableName]; let results = await this.mysql.exec(query, params); res.status(200).json(results); diff --git a/lib/xsql.js b/lib/xsql.js index ae931801a7..b06c96f1e6 100644 --- a/lib/xsql.js +++ b/lib/xsql.js @@ -162,7 +162,7 @@ class Xsql { } if ('_p' in reqParams && parseInt(reqParams._p) > 0) { - reqParams._index = (parseInt(reqParams._p)-1) * reqParams._len + 1; + reqParams._index = (parseInt(reqParams._p) - 1) * reqParams._len + 1; } //console.log(reqParams._index, reqParams._len); @@ -362,17 +362,20 @@ class Xsql { } - prepareRoute(httpType, apiPrefix, urlRoute) { + prepareRoute(internal, httpType, apiPrefix, urlRoute, routeType) { let route = {}; route['httpType'] = httpType; route['routeUrl'] = apiPrefix + urlRoute; + if (internal) { + route['routeType'] = routeType; + } return route; } - getSchemaRoutes(apiPrefix) { + getSchemaRoutes(internal, apiPrefix) { let schemaRoutes = []; @@ -385,17 +388,18 @@ class Xsql { tableObj['resource'] = tableName; - routes.push(this.prepareRoute('get', apiPrefix, tableName)) - routes.push(this.prepareRoute('post', apiPrefix, tableName)) - routes.push(this.prepareRoute('get', apiPrefix, tableName + '/:id')) - routes.push(this.prepareRoute('put', apiPrefix, tableName + '/:id')) - routes.push(this.prepareRoute('delete', apiPrefix, tableName + '/:id')) - routes.push(this.prepareRoute('get', apiPrefix, tableName + '/count')) - routes.push(this.prepareRoute('get', apiPrefix, tableName + '/:id/exists')) + routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/describe', 'describe')) + routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/count', 'count')) + routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName, 'create')) + routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName, 'list')) + routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/:id', 'read')) + routes.push(this.prepareRoute(internal, 'put', apiPrefix, tableName + '/:id', 'update')) + routes.push(this.prepareRoute(internal, 'delete', apiPrefix, tableName + '/:id', 'delete')) + routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/:id/exists', 'exists')) for (var j = 0; j < table['foreignKeys'].length; ++j) { let fk = table['foreignKeys'][j] - routes.push(this.prepareRoute('get', apiPrefix, fk['referenced_table_name'] + '/:id/' + fk['table_name'])) + routes.push(this.prepareRoute(internal, 'get', apiPrefix, fk['referenced_table_name'] + '/:id/' + fk['table_name'], 'relational')) } tableObj['routes'] = routes; @@ -413,7 +417,6 @@ class Xsql { let r = [] r.push(apiPrefix + "tables") - r.push(apiPrefix + ":tableName/describe") if (this.sqlConfig.dynamic) r.push("/dynamic") diff --git a/package.json b/package.json index 725149c611..22e34c9ee9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmysql", - "version": "0.0.3", + "version": "0.0.4", "description": "One command to generate REST APIs for any MySql database", "main": "index.js", "scripts": {