多维表格
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.
 
 
 
 
 
 

508 lines
14 KiB

"use strict";
var Xsql = require("./xsql.js");
var Xctrl = require("./xctrl.js");
var multer = require("multer");
const path = require("path");
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
stat.routines = 0
// console.log('this.config while setting up routes', this.config);
// 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.apis += 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 each 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":
if (!this.config.readOnly)
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":
if (!this.config.readOnly) {
this.app
.route(routes[i]["routeUrl"])
.post(
this.asyncMiddleware(
resourceCtrl.bulkInsert.bind(resourceCtrl)
)
);
}
break;
case "bulkRead":
if (!this.config.readOnly) {
this.app
.route(routes[i]["routeUrl"])
.get(
this.asyncMiddleware(resourceCtrl.bulkRead.bind(resourceCtrl))
);
} else {
stat.apis--;
}
break;
case "bulkDelete":
if (!this.config.readOnly) {
this.app
.route(routes[i]["routeUrl"])
.delete(
this.asyncMiddleware(
resourceCtrl.bulkDelete.bind(resourceCtrl)
)
);
} else {
stat.apis--;
}
break;
case "patch":
if (!this.config.readOnly) {
this.app
.route(routes[i]["routeUrl"])
.patch(
this.asyncMiddleware(resourceCtrl.patch.bind(resourceCtrl))
);
} else {
stat.apis--;
}
break;
case "update":
if (!this.config.readOnly) {
this.app
.route(routes[i]["routeUrl"])
.put(
this.asyncMiddleware(resourceCtrl.update.bind(resourceCtrl))
);
} else {
stat.apis--;
}
break;
case "delete":
if (!this.config.readOnly) {
this.app
.route(routes[i]["routeUrl"])
.delete(
this.asyncMiddleware(resourceCtrl.delete.bind(resourceCtrl))
);
} else {
stat.apis--;
}
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.config.readOnly) {
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 ****************/
stat.apis += 4;
}
/**************** START : health and version ****************/
this.app.get("/_health", this.asyncMiddleware(this.health.bind(this)));
this.app.get("/_version", this.asyncMiddleware(this.version.bind(this)));
stat.apis += 2;
/**************** END : health and version ****************/
/**************** START : call stored procedures ****************/
this.app.get('/_proc', this.asyncMiddleware(this.proc.bind(this)))
stat.apis += 1
const procResources = this.mysql.getProcList(true, this.config.apiPrefix)
this.app.post('/_proc/:proc', this.asyncMiddleware(this.callProc.bind(this)))
stat.routines += procResources.length
stat.apis += procResources.length
/**************** END : call stored procedures ****************/
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(' Number of Routines : %s', stat.routines);
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);
if (obj.query.length) {
let results = await this.mysql.exec(obj.query, obj.params);
res.status(200).json(results);
} else {
res.status(400).json({ err: "Invalid Xjoin request" });
}
}
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"] = this.app.get("version");
version["mysql"] = await this.getMysqlHealth();
version["node"] = process.versions.node;
res.json(version);
}
/**************** END : health and version ****************/
async proc(req, res) {
let query = 'SELECT routine_name AS resource FROM information_schema.routines WHERE routine_schema = ? ';
let params = [this.config.database];
let results = await this.mysql.exec(query, params)
res.status(200).json(results)
}
async callProc(req, res) {
let query = 'CALL ??(?)'
let params = [req.params.proc, Object.values(req.body)]
let results = await this.mysql.exec(query, params)
res.status(200).json(results)
}
}
//expose class
module.exports = Xapi;