多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

432 lines
12 KiB

'use strict';
var Xsql = require('./xsql.js');
var Xctrl = require('./xctrl.js');
var multer = require('multer');
const path = require('path');
const pkginfo = require('pkginfo')(module);
const v8 = require('v8'),
os = require('os');
//define class
class Xapi {
constructor(args, mysqlPool, app) {
this.config = args;
this.mysql = new Xsql(args, mysqlPool)
this.app = app;
this.ctrls = [];
/**************** START : multer ****************/
this.storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, process.cwd())
},
filename: function (req, file, cb) {
console.log(file);
cb(null, Date.now() + '-' + file.originalname)
}
})
this.upload = multer({storage: this.storage})
/**************** END : multer ****************/
}
init(cbk) {
this.mysql.init((err, results) => {
this.app.use(this.urlMiddleware.bind(this))
let stat = this.setupRoutes()
this.app.use(this.errorMiddleware.bind(this))
cbk(err, stat)
})
}
urlMiddleware(req, res, next) {
// get only request url from originalUrl
let justUrl = req.originalUrl.split('?')[0]
let pathSplit = []
// split by apiPrefix
let apiSuffix = justUrl.split(this.config.apiPrefix)
if (apiSuffix.length === 2) {
// split by /
pathSplit = apiSuffix[1].split('/')
if (pathSplit.length) {
if (pathSplit.length >= 3) {
// handle for relational routes
req.app.locals._parentTable = pathSplit[0]
req.app.locals._childTable = pathSplit[2]
} else {
// handles rest of routes
req.app.locals._tableName = pathSplit[0]
}
}
}
next();
}
errorMiddleware(err, req, res, next) {
if (err && err.code)
res.status(400).json({error: err});
else if (err && err.message)
res.status(500).json({error: 'Internal server error : ' + err.message});
else
res.status(500).json({error: 'Internal server error : ' + err});
next(err);
}
asyncMiddleware(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch((err) => {
next(err);
});
}
}
root(req, res) {
let routes = [];
routes = this.mysql.getSchemaRoutes(false, req.protocol + '://' + req.get('host') + this.config.apiPrefix);
routes = routes.concat(this.mysql.globalRoutesPrint(req.protocol + '://' + req.get('host') + this.config.apiPrefix))
res.json(routes)
}
setupRoutes() {
let stat = {}
stat.tables = 0
stat.apis = 0
// show routes for database schema
this.app.get('/', this.asyncMiddleware(this.root.bind(this)))
// show all resouces
this.app.route(this.config.apiPrefix + 'tables')
.get(this.asyncMiddleware(this.tables.bind(this)));
this.app.route(this.config.apiPrefix + 'xjoin')
.get(this.asyncMiddleware(this.xjoin.bind(this)));
stat.api += 3;
/**************** START : setup routes for each table ****************/
let resources = [];
resources = this.mysql.getSchemaRoutes(true, this.config.apiPrefix);
stat.tables += resources.length
// iterate over each resource
for (var j = 0; j < resources.length; ++j) {
let resourceCtrl = new Xctrl(this.app, this.mysql)
this.ctrls.push(resourceCtrl);
let routes = resources[j]['routes'];
stat.apis += resources[j]['routes'].length
// iterate over rach routes in resource and map function
for (var i = 0; i < routes.length; ++i) {
switch (routes[i]['routeType']) {
case 'list':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.list.bind(resourceCtrl)));
break;
case 'findOne':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.findOne.bind(resourceCtrl)));
break;
case 'create':
this.app.route(routes[i]['routeUrl'])
.post(this.asyncMiddleware(resourceCtrl.create.bind(resourceCtrl)));
break;
case 'read':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.read.bind(resourceCtrl)));
break;
case 'bulkInsert':
this.app.route(routes[i]['routeUrl'])
.post(this.asyncMiddleware(resourceCtrl.bulkInsert.bind(resourceCtrl)));
break;
case 'bulkRead':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.bulkRead.bind(resourceCtrl)));
break;
case 'bulkDelete':
this.app.route(routes[i]['routeUrl'])
.delete(this.asyncMiddleware(resourceCtrl.bulkDelete.bind(resourceCtrl)));
break;
case 'patch':
this.app.route(routes[i]['routeUrl'])
.patch(this.asyncMiddleware(resourceCtrl.patch.bind(resourceCtrl)));
break;
case 'update':
this.app.route(routes[i]['routeUrl'])
.put(this.asyncMiddleware(resourceCtrl.update.bind(resourceCtrl)));
break;
case 'delete':
this.app.route(routes[i]['routeUrl'])
.delete(this.asyncMiddleware(resourceCtrl.delete.bind(resourceCtrl)));
break;
case 'exists':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.exists.bind(resourceCtrl)));
break;
case 'count':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.count.bind(resourceCtrl)));
break;
case 'distinct':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.distinct.bind(resourceCtrl)));
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(resourceCtrl.nestedList.bind(resourceCtrl)));
break;
case 'groupby':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.groupBy.bind(resourceCtrl)));
break;
case 'ugroupby':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.ugroupby.bind(resourceCtrl)));
break;
case 'chart':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.chart.bind(resourceCtrl)));
break;
case 'autoChart':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.autoChart.bind(resourceCtrl)));
break;
case 'aggregate':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(resourceCtrl.aggregate.bind(resourceCtrl)));
break;
}
}
}
/**************** END : setup routes for each table ****************/
if (this.config.dynamic === 1) {
this.app.route('/dynamic*')
.post(this.asyncMiddleware(this.runQuery.bind(this)));
/**************** START : multer routes ****************/
this.app.post('/upload', this.upload.single('file'), this.uploadFile.bind(this));
this.app.post('/uploads', this.upload.array('files', 10), this.uploadFiles.bind(this));
this.app.get('/download', this.downloadFile.bind(this));
/**************** END : multer routes ****************/
/**************** START : health and version ****************/
this.app.get('/_health', this.asyncMiddleware(this.health.bind(this)));
this.app.get('/_version', this.asyncMiddleware(this.version.bind(this)));
/**************** END : health and version ****************/
stat.api += 4;
}
let statStr = ' Generated: ' + stat.apis + ' REST APIs for ' + stat.tables + ' tables '
console.log(' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ');
console.log(' ');
console.log(' Database : %s', this.config.database);
console.log(' Number of Tables : %s', stat.tables);
console.log(' ');
console.log(' REST APIs Generated : %s'.green.bold, stat.apis);
console.log(' ');
return stat
}
async xjoin(req, res) {
let obj = {}
obj.query = '';
obj.params = [];
this.mysql.prepareJoinQuery(req, res, obj)
//console.log(obj);
let results = await this.mysql.exec(obj.query, obj.params)
res.status(200).json(results)
}
async tableDescribe(req, res) {
let query = 'describe ??';
let params = [req.app.locals._tableName];
let results = await this.mysql.exec(query, params);
res.status(200).json(results);
}
async tables(req, res) {
let query = 'SELECT table_name AS resource FROM information_schema.tables WHERE table_schema = ? ';
let params = [this.config.database];
if (Object.keys(this.config.ignoreTables).length > 0) {
query += 'and table_name not in (?)'
params.push(Object.keys(this.config.ignoreTables))
}
let results = await this.mysql.exec(query, params)
res.status(200).json(results)
}
async runQuery(req, res) {
let query = req.body.query;
let params = req.body.params;
let results = await this.mysql.exec(query, params);
res.status(200).json(results);
}
/**************** START : files related ****************/
downloadFile(req, res) {
let file = path.join(process.cwd(), req.query.name);
res.download(file);
}
uploadFile(req, res) {
if (req.file) {
console.log(req.file.path);
res.end(req.file.path);
} else {
res.end('upload failed');
}
}
uploadFiles(req, res) {
if (!req.files || req.files.length === 0) {
res.end('upload failed')
} else {
let files = [];
for (let i = 0; i < req.files.length; ++i) {
files.push(req.files[i].path);
}
res.end(files.toString());
}
}
/**************** END : files related ****************/
/**************** START : health and version ****************/
async getMysqlUptime() {
let v = await this.mysql.exec('SHOW GLOBAL STATUS LIKE \'Uptime\';', []);
return v[0]['Value'];
}
async getMysqlHealth() {
let v = await this.mysql.exec('select version() as version', []);
return v[0]['version'];
}
async health(req, res) {
let status = {};
status['process_uptime'] = process.uptime();
status['mysql_uptime'] = await this.getMysqlUptime();
if (Object.keys(req.query).length) {
status['process_memory_usage'] = process.memoryUsage();
status['os_total_memory'] = os.totalmem();
status['os_free_memory'] = os.freemem();
status['os_load_average'] = os.loadavg();
status['v8_heap_statistics'] = v8.getHeapStatistics();
}
res.json(status);
}
async version(req, res) {
let version = {};
version['Xmysql'] = pkginfo.version;
version['mysql'] = await this.getMysqlHealth();
version['node'] = process.versions.node;
res.json(version);
}
/**************** END : health and version ****************/
}
//expose class
module.exports = Xapi;