xmysql-timzoned 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +28 -0
- package/.travis.yml +17 -0
- package/LICENSE +22 -0
- package/bin/index.js +94 -0
- package/dockerfile +33 -0
- package/index.js +1 -0
- package/lib/util/cmd.helper.js +109 -0
- package/lib/util/data.helper.js +211 -0
- package/lib/util/whereClause.helper.js +350 -0
- package/lib/xapi.js +509 -0
- package/lib/xctrl.js +589 -0
- package/lib/xsql.js +1321 -0
- package/package.json +46 -0
package/lib/xapi.js
ADDED
@@ -0,0 +1,509 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
var Xsql = require("./xsql.js");
|
4
|
+
var Xctrl = require("./xctrl.js");
|
5
|
+
var multer = require("multer");
|
6
|
+
const path = require("path");
|
7
|
+
|
8
|
+
const v8 = require("v8"),
|
9
|
+
os = require("os");
|
10
|
+
|
11
|
+
|
12
|
+
//define class
|
13
|
+
class Xapi {
|
14
|
+
constructor(args, mysqlPool, app) {
|
15
|
+
this.config = args;
|
16
|
+
this.mysql = new Xsql(args, mysqlPool);
|
17
|
+
this.app = app;
|
18
|
+
this.ctrls = [];
|
19
|
+
|
20
|
+
/**************** START : multer ****************/
|
21
|
+
this.storage = multer.diskStorage({
|
22
|
+
destination: function(req, file, cb) {
|
23
|
+
cb(null, process.cwd());
|
24
|
+
},
|
25
|
+
filename: function(req, file, cb) {
|
26
|
+
console.log(file);
|
27
|
+
cb(null, Date.now() + "-" + file.originalname);
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
this.upload = multer({ storage: this.storage });
|
32
|
+
/**************** END : multer ****************/
|
33
|
+
}
|
34
|
+
|
35
|
+
init(cbk) {
|
36
|
+
this.mysql.init((err, results) => {
|
37
|
+
this.app.use(this.urlMiddleware.bind(this));
|
38
|
+
let stat = this.setupRoutes();
|
39
|
+
this.app.use(this.errorMiddleware.bind(this));
|
40
|
+
cbk(err, stat);
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
44
|
+
urlMiddleware(req, res, next) {
|
45
|
+
// get only request url from originalUrl
|
46
|
+
let justUrl = req.originalUrl.split("?")[0];
|
47
|
+
let pathSplit = [];
|
48
|
+
|
49
|
+
// split by apiPrefix
|
50
|
+
let apiSuffix = justUrl.split(this.config.apiPrefix);
|
51
|
+
|
52
|
+
if (apiSuffix.length === 2) {
|
53
|
+
// split by /
|
54
|
+
pathSplit = apiSuffix[1].split("/");
|
55
|
+
if (pathSplit.length) {
|
56
|
+
if (pathSplit.length >= 3) {
|
57
|
+
// handle for relational routes
|
58
|
+
req.app.locals._parentTable = pathSplit[0];
|
59
|
+
req.app.locals._childTable = pathSplit[2];
|
60
|
+
} else {
|
61
|
+
// handles rest of routes
|
62
|
+
req.app.locals._tableName = pathSplit[0];
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
next();
|
68
|
+
}
|
69
|
+
|
70
|
+
errorMiddleware(err, req, res, next) {
|
71
|
+
if (err && err.code) res.status(400).json({ error: err });
|
72
|
+
else if (err && err.message)
|
73
|
+
res.status(500).json({ error: "Internal server error : " + err.message });
|
74
|
+
else res.status(500).json({ error: "Internal server error : " + err });
|
75
|
+
|
76
|
+
next(err);
|
77
|
+
}
|
78
|
+
|
79
|
+
asyncMiddleware(fn) {
|
80
|
+
return (req, res, next) => {
|
81
|
+
Promise.resolve(fn(req, res, next)).catch(err => {
|
82
|
+
next(err);
|
83
|
+
});
|
84
|
+
};
|
85
|
+
}
|
86
|
+
|
87
|
+
root(req, res) {
|
88
|
+
let routes = [];
|
89
|
+
routes = this.mysql.getSchemaRoutes(
|
90
|
+
false,
|
91
|
+
req.protocol + "://" + req.get("host") + this.config.apiPrefix
|
92
|
+
);
|
93
|
+
routes = routes.concat(
|
94
|
+
this.mysql.globalRoutesPrint(
|
95
|
+
req.protocol + "://" + req.get("host") + this.config.apiPrefix
|
96
|
+
)
|
97
|
+
);
|
98
|
+
res.json(routes);
|
99
|
+
}
|
100
|
+
|
101
|
+
setupRoutes() {
|
102
|
+
let stat = {}
|
103
|
+
stat.tables = 0
|
104
|
+
stat.apis = 0
|
105
|
+
stat.routines = 0
|
106
|
+
|
107
|
+
// console.log('this.config while setting up routes', this.config);
|
108
|
+
|
109
|
+
// show routes for database schema
|
110
|
+
this.app.get("/", this.asyncMiddleware(this.root.bind(this)));
|
111
|
+
|
112
|
+
// show all resouces
|
113
|
+
this.app
|
114
|
+
.route(this.config.apiPrefix + "tables")
|
115
|
+
.get(this.asyncMiddleware(this.tables.bind(this)));
|
116
|
+
|
117
|
+
this.app
|
118
|
+
.route(this.config.apiPrefix + "xjoin")
|
119
|
+
.get(this.asyncMiddleware(this.xjoin.bind(this)));
|
120
|
+
|
121
|
+
stat.apis += 3;
|
122
|
+
|
123
|
+
/**************** START : setup routes for each table ****************/
|
124
|
+
let resources = [];
|
125
|
+
resources = this.mysql.getSchemaRoutes(true, this.config.apiPrefix);
|
126
|
+
|
127
|
+
stat.tables += resources.length;
|
128
|
+
|
129
|
+
// iterate over each resource
|
130
|
+
for (var j = 0; j < resources.length; ++j) {
|
131
|
+
let resourceCtrl = new Xctrl(this.app, this.mysql);
|
132
|
+
this.ctrls.push(resourceCtrl);
|
133
|
+
|
134
|
+
let routes = resources[j]["routes"];
|
135
|
+
|
136
|
+
stat.apis += resources[j]["routes"].length;
|
137
|
+
|
138
|
+
// iterate over each routes in resource and map function
|
139
|
+
for (var i = 0; i < routes.length; ++i) {
|
140
|
+
switch (routes[i]["routeType"]) {
|
141
|
+
case "list":
|
142
|
+
this.app
|
143
|
+
.route(routes[i]["routeUrl"])
|
144
|
+
.get(this.asyncMiddleware(resourceCtrl.list.bind(resourceCtrl)));
|
145
|
+
break;
|
146
|
+
|
147
|
+
case "findOne":
|
148
|
+
this.app
|
149
|
+
.route(routes[i]["routeUrl"])
|
150
|
+
.get(
|
151
|
+
this.asyncMiddleware(resourceCtrl.findOne.bind(resourceCtrl))
|
152
|
+
);
|
153
|
+
break;
|
154
|
+
|
155
|
+
case "create":
|
156
|
+
if (!this.config.readOnly)
|
157
|
+
this.app
|
158
|
+
.route(routes[i]["routeUrl"])
|
159
|
+
.post(
|
160
|
+
this.asyncMiddleware(resourceCtrl.create.bind(resourceCtrl))
|
161
|
+
);
|
162
|
+
break;
|
163
|
+
|
164
|
+
case "read":
|
165
|
+
this.app
|
166
|
+
.route(routes[i]["routeUrl"])
|
167
|
+
.get(this.asyncMiddleware(resourceCtrl.read.bind(resourceCtrl)));
|
168
|
+
break;
|
169
|
+
|
170
|
+
case "bulkInsert":
|
171
|
+
if (!this.config.readOnly) {
|
172
|
+
this.app
|
173
|
+
.route(routes[i]["routeUrl"])
|
174
|
+
.post(
|
175
|
+
this.asyncMiddleware(
|
176
|
+
resourceCtrl.bulkInsert.bind(resourceCtrl)
|
177
|
+
)
|
178
|
+
);
|
179
|
+
}
|
180
|
+
break;
|
181
|
+
|
182
|
+
case "bulkRead":
|
183
|
+
if (!this.config.readOnly) {
|
184
|
+
this.app
|
185
|
+
.route(routes[i]["routeUrl"])
|
186
|
+
.get(
|
187
|
+
this.asyncMiddleware(resourceCtrl.bulkRead.bind(resourceCtrl))
|
188
|
+
);
|
189
|
+
} else {
|
190
|
+
stat.apis--;
|
191
|
+
}
|
192
|
+
break;
|
193
|
+
|
194
|
+
case "bulkDelete":
|
195
|
+
if (!this.config.readOnly) {
|
196
|
+
this.app
|
197
|
+
.route(routes[i]["routeUrl"])
|
198
|
+
.delete(
|
199
|
+
this.asyncMiddleware(
|
200
|
+
resourceCtrl.bulkDelete.bind(resourceCtrl)
|
201
|
+
)
|
202
|
+
);
|
203
|
+
} else {
|
204
|
+
stat.apis--;
|
205
|
+
}
|
206
|
+
break;
|
207
|
+
|
208
|
+
case "patch":
|
209
|
+
if (!this.config.readOnly) {
|
210
|
+
this.app
|
211
|
+
.route(routes[i]["routeUrl"])
|
212
|
+
.patch(
|
213
|
+
this.asyncMiddleware(resourceCtrl.patch.bind(resourceCtrl))
|
214
|
+
);
|
215
|
+
} else {
|
216
|
+
stat.apis--;
|
217
|
+
}
|
218
|
+
break;
|
219
|
+
|
220
|
+
case "update":
|
221
|
+
if (!this.config.readOnly) {
|
222
|
+
this.app
|
223
|
+
.route(routes[i]["routeUrl"])
|
224
|
+
.put(
|
225
|
+
this.asyncMiddleware(resourceCtrl.update.bind(resourceCtrl))
|
226
|
+
);
|
227
|
+
} else {
|
228
|
+
stat.apis--;
|
229
|
+
}
|
230
|
+
break;
|
231
|
+
|
232
|
+
case "delete":
|
233
|
+
if (!this.config.readOnly) {
|
234
|
+
this.app
|
235
|
+
.route(routes[i]["routeUrl"])
|
236
|
+
.delete(
|
237
|
+
this.asyncMiddleware(resourceCtrl.delete.bind(resourceCtrl))
|
238
|
+
);
|
239
|
+
} else {
|
240
|
+
stat.apis--;
|
241
|
+
}
|
242
|
+
break;
|
243
|
+
|
244
|
+
case "exists":
|
245
|
+
this.app
|
246
|
+
.route(routes[i]["routeUrl"])
|
247
|
+
.get(
|
248
|
+
this.asyncMiddleware(resourceCtrl.exists.bind(resourceCtrl))
|
249
|
+
);
|
250
|
+
break;
|
251
|
+
|
252
|
+
case "count":
|
253
|
+
this.app
|
254
|
+
.route(routes[i]["routeUrl"])
|
255
|
+
.get(this.asyncMiddleware(resourceCtrl.count.bind(resourceCtrl)));
|
256
|
+
break;
|
257
|
+
|
258
|
+
case "distinct":
|
259
|
+
this.app
|
260
|
+
.route(routes[i]["routeUrl"])
|
261
|
+
.get(
|
262
|
+
this.asyncMiddleware(resourceCtrl.distinct.bind(resourceCtrl))
|
263
|
+
);
|
264
|
+
break;
|
265
|
+
|
266
|
+
case "describe":
|
267
|
+
this.app
|
268
|
+
.route(routes[i]["routeUrl"])
|
269
|
+
.get(this.asyncMiddleware(this.tableDescribe.bind(this)));
|
270
|
+
break;
|
271
|
+
|
272
|
+
case "relational":
|
273
|
+
this.app
|
274
|
+
.route(routes[i]["routeUrl"])
|
275
|
+
.get(
|
276
|
+
this.asyncMiddleware(resourceCtrl.nestedList.bind(resourceCtrl))
|
277
|
+
);
|
278
|
+
break;
|
279
|
+
|
280
|
+
case "groupby":
|
281
|
+
this.app
|
282
|
+
.route(routes[i]["routeUrl"])
|
283
|
+
.get(
|
284
|
+
this.asyncMiddleware(resourceCtrl.groupBy.bind(resourceCtrl))
|
285
|
+
);
|
286
|
+
break;
|
287
|
+
|
288
|
+
case "ugroupby":
|
289
|
+
this.app
|
290
|
+
.route(routes[i]["routeUrl"])
|
291
|
+
.get(
|
292
|
+
this.asyncMiddleware(resourceCtrl.ugroupby.bind(resourceCtrl))
|
293
|
+
);
|
294
|
+
break;
|
295
|
+
|
296
|
+
case "chart":
|
297
|
+
this.app
|
298
|
+
.route(routes[i]["routeUrl"])
|
299
|
+
.get(this.asyncMiddleware(resourceCtrl.chart.bind(resourceCtrl)));
|
300
|
+
break;
|
301
|
+
|
302
|
+
case "autoChart":
|
303
|
+
this.app
|
304
|
+
.route(routes[i]["routeUrl"])
|
305
|
+
.get(
|
306
|
+
this.asyncMiddleware(resourceCtrl.autoChart.bind(resourceCtrl))
|
307
|
+
);
|
308
|
+
break;
|
309
|
+
|
310
|
+
case "aggregate":
|
311
|
+
this.app
|
312
|
+
.route(routes[i]["routeUrl"])
|
313
|
+
.get(
|
314
|
+
this.asyncMiddleware(resourceCtrl.aggregate.bind(resourceCtrl))
|
315
|
+
);
|
316
|
+
break;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
}
|
320
|
+
/**************** END : setup routes for each table ****************/
|
321
|
+
|
322
|
+
if (this.config.dynamic === 1 && !this.config.readOnly) {
|
323
|
+
this.app
|
324
|
+
.route("/dynamic*")
|
325
|
+
.post(this.asyncMiddleware(this.runQuery.bind(this)));
|
326
|
+
|
327
|
+
/**************** START : multer routes ****************/
|
328
|
+
this.app.post(
|
329
|
+
"/upload",
|
330
|
+
this.upload.single("file"),
|
331
|
+
this.uploadFile.bind(this)
|
332
|
+
);
|
333
|
+
this.app.post(
|
334
|
+
"/uploads",
|
335
|
+
this.upload.array("files", 10),
|
336
|
+
this.uploadFiles.bind(this)
|
337
|
+
);
|
338
|
+
this.app.get("/download", this.downloadFile.bind(this));
|
339
|
+
/**************** END : multer routes ****************/
|
340
|
+
|
341
|
+
stat.apis += 4;
|
342
|
+
}
|
343
|
+
|
344
|
+
/**************** START : health and version ****************/
|
345
|
+
this.app.get("/_health", this.asyncMiddleware(this.health.bind(this)));
|
346
|
+
this.app.get("/_version", this.asyncMiddleware(this.version.bind(this)));
|
347
|
+
stat.apis += 2;
|
348
|
+
/**************** END : health and version ****************/
|
349
|
+
|
350
|
+
/**************** START : call stored procedures ****************/
|
351
|
+
this.app.get('/_proc', this.asyncMiddleware(this.proc.bind(this)))
|
352
|
+
stat.apis += 1
|
353
|
+
const procResources = this.mysql.getProcList(true, this.config.apiPrefix)
|
354
|
+
this.app.post('/_proc/:proc', this.asyncMiddleware(this.callProc.bind(this)))
|
355
|
+
stat.routines += procResources.length
|
356
|
+
stat.apis += procResources.length
|
357
|
+
/**************** END : call stored procedures ****************/
|
358
|
+
|
359
|
+
|
360
|
+
let statStr = ' Generated: ' + stat.apis + ' REST APIs for ' + stat.tables + ' tables '
|
361
|
+
|
362
|
+
console.log(' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ');
|
363
|
+
console.log(' ');
|
364
|
+
console.log(' Database : %s', this.config.database);
|
365
|
+
console.log(' Number of Tables : %s', stat.tables);
|
366
|
+
console.log(' Number of Routines : %s', stat.routines);
|
367
|
+
console.log(' ');
|
368
|
+
console.log(' REST APIs Generated : %s'.green.bold, stat.apis);
|
369
|
+
console.log(' ');
|
370
|
+
|
371
|
+
return stat
|
372
|
+
|
373
|
+
}
|
374
|
+
|
375
|
+
async xjoin(req, res) {
|
376
|
+
let obj = {};
|
377
|
+
|
378
|
+
obj.query = "";
|
379
|
+
obj.params = [];
|
380
|
+
|
381
|
+
this.mysql.prepareJoinQuery(req, res, obj);
|
382
|
+
|
383
|
+
//console.log(obj);
|
384
|
+
if (obj.query.length) {
|
385
|
+
let results = await this.mysql.exec(obj.query, obj.params);
|
386
|
+
res.status(200).json(results);
|
387
|
+
} else {
|
388
|
+
res.status(400).json({ err: "Invalid Xjoin request" });
|
389
|
+
}
|
390
|
+
}
|
391
|
+
|
392
|
+
async tableDescribe(req, res) {
|
393
|
+
let query = "describe ??";
|
394
|
+
let params = [req.app.locals._tableName];
|
395
|
+
|
396
|
+
let results = await this.mysql.exec(query, params);
|
397
|
+
res.status(200).json(results);
|
398
|
+
}
|
399
|
+
|
400
|
+
async tables(req, res) {
|
401
|
+
let query =
|
402
|
+
"SELECT table_name AS resource FROM information_schema.tables WHERE table_schema = ? ";
|
403
|
+
let params = [this.config.database];
|
404
|
+
|
405
|
+
if (Object.keys(this.config.ignoreTables).length > 0) {
|
406
|
+
query += "and table_name not in (?)";
|
407
|
+
params.push(Object.keys(this.config.ignoreTables));
|
408
|
+
}
|
409
|
+
|
410
|
+
let results = await this.mysql.exec(query, params);
|
411
|
+
|
412
|
+
res.status(200).json(results);
|
413
|
+
}
|
414
|
+
|
415
|
+
async runQuery(req, res) {
|
416
|
+
let query = req.body.query;
|
417
|
+
let params = req.body.params;
|
418
|
+
|
419
|
+
let results = await this.mysql.exec(query, params);
|
420
|
+
res.status(200).json(results);
|
421
|
+
}
|
422
|
+
|
423
|
+
/**************** START : files related ****************/
|
424
|
+
downloadFile(req, res) {
|
425
|
+
let file = path.join(process.cwd(), req.query.name);
|
426
|
+
res.download(file);
|
427
|
+
}
|
428
|
+
|
429
|
+
uploadFile(req, res) {
|
430
|
+
if (req.file) {
|
431
|
+
console.log(req.file.path);
|
432
|
+
res.end(req.file.path);
|
433
|
+
} else {
|
434
|
+
res.end("upload failed");
|
435
|
+
}
|
436
|
+
}
|
437
|
+
|
438
|
+
uploadFiles(req, res) {
|
439
|
+
if (!req.files || req.files.length === 0) {
|
440
|
+
res.end("upload failed");
|
441
|
+
} else {
|
442
|
+
let files = [];
|
443
|
+
for (let i = 0; i < req.files.length; ++i) {
|
444
|
+
files.push(req.files[i].path);
|
445
|
+
}
|
446
|
+
|
447
|
+
res.end(files.toString());
|
448
|
+
}
|
449
|
+
}
|
450
|
+
|
451
|
+
/**************** END : files related ****************/
|
452
|
+
|
453
|
+
/**************** START : health and version ****************/
|
454
|
+
|
455
|
+
async getMysqlUptime() {
|
456
|
+
let v = await this.mysql.exec("SHOW GLOBAL STATUS LIKE 'Uptime';", []);
|
457
|
+
return v[0]["Value"];
|
458
|
+
}
|
459
|
+
|
460
|
+
async getMysqlHealth() {
|
461
|
+
let v = await this.mysql.exec("select version() as version", []);
|
462
|
+
return v[0]["version"];
|
463
|
+
}
|
464
|
+
|
465
|
+
async health(req, res) {
|
466
|
+
let status = {};
|
467
|
+
status["process_uptime"] = process.uptime();
|
468
|
+
status["mysql_uptime"] = await this.getMysqlUptime();
|
469
|
+
|
470
|
+
if (Object.keys(req.query).length) {
|
471
|
+
status["process_memory_usage"] = process.memoryUsage();
|
472
|
+
status["os_total_memory"] = os.totalmem();
|
473
|
+
status["os_free_memory"] = os.freemem();
|
474
|
+
status["os_load_average"] = os.loadavg();
|
475
|
+
status["v8_heap_statistics"] = v8.getHeapStatistics();
|
476
|
+
}
|
477
|
+
|
478
|
+
res.json(status);
|
479
|
+
}
|
480
|
+
|
481
|
+
async version(req, res) {
|
482
|
+
let version = {};
|
483
|
+
|
484
|
+
version["Xmysql"] = this.app.get("version");
|
485
|
+
version["mysql"] = await this.getMysqlHealth();
|
486
|
+
version["node"] = process.versions.node;
|
487
|
+
res.json(version);
|
488
|
+
}
|
489
|
+
|
490
|
+
/**************** END : health and version ****************/
|
491
|
+
|
492
|
+
async proc(req, res) {
|
493
|
+
let query = 'SELECT routine_name AS resource FROM information_schema.routines WHERE routine_schema = ? ';
|
494
|
+
let params = [this.config.database];
|
495
|
+
let results = await this.mysql.exec(query, params)
|
496
|
+
res.status(200).json(results)
|
497
|
+
}
|
498
|
+
|
499
|
+
async callProc(req, res) {
|
500
|
+
let query = 'CALL ??(?)'
|
501
|
+
let params = [req.params.proc, Object.values(req.body)]
|
502
|
+
let results = await this.mysql.exec(query, params)
|
503
|
+
res.status(200).json(results)
|
504
|
+
}
|
505
|
+
|
506
|
+
}
|
507
|
+
|
508
|
+
//expose class
|
509
|
+
module.exports = Xapi;
|