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/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;