├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── README.md
├── assets
├── doubon copy.png
├── doubon.png
├── facebook.png
├── google.png
├── hn.jpg
├── hn1.png
├── linkedin.png
├── log.gif
├── out.gif
├── pinterest.png
├── reddit.png
├── renren.png
├── rick-and-morty.gif
├── stumbleupon.png
├── twitter.png
├── vk.png
├── weibo.png
└── wykop.png
├── bin
└── index.js
├── dev.Dockerfile
├── docker-compose.yml
├── docker-entrypoint.sh
├── dockerfile
├── examples
└── aws-lambda
│ └── index.js
├── index.js
├── lib
├── util
│ ├── cmd.helper.js
│ ├── data.helper.js
│ └── whereClause.helper.js
├── xapi.js
├── xctrl.js
└── xsql.js
├── package-lock.json
├── package.json
└── tests
├── docker-sample.sql
├── sample.sql
└── tests.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["eslint-config-airbnb-base", "eslint-config-prettier"],
3 | plugins: ["eslint-plugin-import", "eslint-plugin-prettier"],
4 | parserOptions: {
5 | ecmaFeatures: {
6 | ecmaVersion: 6
7 | }
8 | },
9 | env: {
10 | es6: true,
11 | node: true
12 | },
13 | rules: {
14 | "prettier/prettier": ["error", {}],
15 | "max-len": ["error", { code: 2000, ignoreUrls: true }],
16 | "linebreak-style": 0,
17 | "no-use-before-define": ["error", { functions: false, classes: false }],
18 | "no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
19 | "no-underscore-dangle": 0,
20 | "import/no-amd": 0,
21 | "import/no-dynamic-require": 0,
22 | "no-console": 0,
23 | "no-param-reassign": 0,
24 | "no-unused-vars": ["error", { argsIgnorePattern: "next" }],
25 | "comma-dangle": 0
26 | }
27 | };
28 |
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS
2 | # ===========
3 | .DS_Store
4 | ehthumbs.db
5 | Icon?
6 | Thumbs.db
7 |
8 | # Node and related ecosystem
9 | # ==========================
10 | .nodemonignore
11 | .sass-cache/
12 | node_modules/
13 | help/
14 | a
15 | public/lib/
16 | app/tests/coverage/
17 | .bower-*/
18 | .idea/
19 | coverage/
20 |
21 |
22 | # MEAN.js app and assets
23 | # ======================
24 | public/dist/
25 | uploads
26 | modules/users/client/img/profile/uploads
27 | config/env/local.js
28 | *.pem
29 |
30 | # Ignoring MEAN.JS's gh-pages branch for documenation
31 | _site/
32 |
33 | # General
34 | # =======
35 | *.log
36 | *.csv
37 | *.dat
38 | *.out
39 | *.pid
40 | *.gz
41 | *.tmp
42 | *.bak
43 | *.swp
44 | logs/
45 | build/
46 | uploads/
47 |
48 | # Sublime editor
49 | # ==============
50 | .sublime-project
51 | *.sublime-project
52 | *.sublime-workspace
53 |
54 | # Eclipse project files
55 | # =====================
56 | .project
57 | .settings/
58 | .*.md.html
59 | .metadata
60 | *~.nib
61 | local.properties
62 |
63 | # IntelliJ
64 | # ========
65 | *.iml
66 |
67 | # Cloud9 IDE
68 | # =========
69 | .c9/
70 | data/
71 | mongod
72 |
73 | # Visual Studio
74 | # =========
75 | *.suo
76 | *.ntvs*
77 | *.njsproj
78 | *.sln
79 |
80 | .history
81 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '7.6.0'
4 | - '8'
5 | - '10'
6 | - '12'
7 | - 'node'
8 | services:
9 | - mysql
10 | install:
11 | - npm install
12 | before_install:
13 | - mysql -e 'CREATE DATABASE IF NOT EXISTS classicmodels;'
14 | - mysql -u root --default-character-set=utf8 classicmodels < tests/sample.sql
15 | script:
16 | - npm test
17 |
18 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contribution guidelines
2 | - Try to keep it close to original design. Key idea here being zero configuration. So automate by going out of the way.
3 | - Tests to cover new functionality.
4 | - If the issue is a major or large change. Please create a PR issue before working on it.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Please provide dev environment versions of your system
4 |
5 | node -v
6 | ( xmysql requires node >= 7.6.0 )
7 |
8 | npm -v
9 | mysql --version
10 | xmysql --version
11 |
12 | - - -
13 |
14 | Provide steps to reproduce this issue
15 | or
16 | Provide minimum code to reproduce this issue
17 | or
18 | HTTP request,response,error snapshot of this issue
19 |
20 | - - -
21 |
22 |
23 | Mark issue with suitable label if it is clear what this issue is.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 o1lab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://raw.githubusercontent.com/o1lab/xmysql/master/LICENSE)
3 |
4 |
5 |
6 | Xmysql is now NocoDB
7 |
8 | ✨ The Open Source Airtable Alternative ✨
9 |
10 |
11 |
12 | https://github.com/nocodb/nocodb
13 |
14 | ## Xmysql : One command to generate REST APIs for any MySql database
15 |
16 | ## Why this ?
17 |
18 |
19 |
20 |
21 | Generating REST APIs for a MySql database which does not follow conventions of
22 | frameworks such as rails, django, laravel etc is a small adventure that one like to avoid ..
23 |
24 | Hence this.
25 |
26 | ## Setup and Usage
27 |
28 | xmysql requires node >= 7.6.0
29 |
30 | ```
31 | npm install -g xmysql
32 | ```
33 | ```
34 | xmysql -h localhost -u mysqlUsername -p mysqlPassword -d databaseName
35 | ```
36 | ```
37 | http://localhost:3000
38 | ```
39 |
40 |
41 | That is it! Simple and minimalistic!
42 |
43 | Happy hackery!
44 |
45 |
46 |
47 |
48 | ## Example : Generate REST APIs for [Magento](http://www.magereverse.com/index/magento-sql-structure/version/1-7-0-2)
49 |
50 | Powered by popular node packages : ([express](https://github.com/expressjs/express), [mysql](https://github.com/mysqljs/mysql)) => { [xmysql](https://github.com/o1lab/xmysql) }
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
Boost Your Hacker Karma By Sharing :
59 |

60 |

61 |

62 |

63 |

64 |

65 |

66 |

67 |

68 |

69 |
70 |
71 |
72 |
73 |
74 | ## Features
75 | * Generates API for **ANY** MySql database :fire::fire:
76 | * Serves APIs irrespective of naming conventions of primary keys, foreign keys, tables etc :fire::fire:
77 | * Support for composite primary keys :fire::fire:
78 | * REST API Usual suspects : CRUD, List, FindOne, Count, Exists, Distinct
79 | * Bulk insert, Bulk delete, Bulk read :fire:
80 | * Relations
81 | * Pagination
82 | * Sorting
83 | * Column filtering - Fields :fire:
84 | * Row filtering - Where :fire:
85 | * Aggregate functions
86 | * Group By, Having (as query params) :fire::fire:
87 | * Group By, Having (as a separate API) :fire::fire:
88 | * Multiple group by in one API :fire::fire::fire::fire:
89 | * Chart API for numeric column :fire::fire::fire::fire::fire::fire:
90 | * Auto Chart API - (a gift for lazy while prototyping) :fire::fire::fire::fire::fire::fire:
91 | * [XJOIN - (Supports any number of JOINS)](#xjoin) :fire::fire::fire::fire::fire::fire::fire::fire::fire:
92 | * Supports views
93 | * Prototyping (features available when using local MySql server only)
94 | * Run dynamic queries :fire::fire::fire:
95 | * Upload single file
96 | * Upload multiple files
97 | * Download file
98 | * Health and version apis
99 | * Use more than one CPU Cores
100 | * [Docker support](#docker) and [Nginx reverse proxy config](#nginx-reverse-proxy-config-with-docker) :fire::fire::fire: - Thanks to [@markuman](https://github.com/markuman)
101 | * AWS Lambda Example - Thanks to [@bertyhell](https://github.com/bertyhell) :fire::fire::fire:
102 |
103 |
104 | Use HTTP clients like [Postman](https://www.getpostman.com/) or [similar tools](https://chrome.google.com/webstore/search/http%20client?_category=apps) to invoke REST API calls
105 |
106 | ____
107 |
108 | Download [node](https://nodejs.org/en/download/current/),
109 | [mysql](https://dev.mysql.com/downloads/mysql/)
110 | [(setup mysql)](https://dev.mysql.com/doc/mysql-getting-started/en/#mysql-getting-started-installing),
111 | [sample database](https://dev.mysql.com/doc/employee/en/employees-installation.html) -
112 | if you haven't on your system.
113 |
114 |
115 | ## API Overview
116 |
117 | | HTTP Type | API URL | Comments |
118 | |-----------|----------------------------------|---------------------------------------------------------
119 | | GET | / | Gets all REST APIs |
120 | | GET | /api/tableName | Lists rows of table |
121 | | POST | /api/tableName | Create a new row |
122 | | PUT | /api/tableName | Replaces existing row with new row |
123 | | POST :fire:| /api/tableName/bulk | Create multiple rows - send object array in request body|
124 | | GET :fire:| /api/tableName/bulk | Lists multiple rows - /api/tableName/bulk?_ids=1,2,3 |
125 | | DELETE :fire:| /api/tableName/bulk | Deletes multiple rows - /api/tableName/bulk?_ids=1,2,3 |
126 | | GET | /api/tableName/:id | Retrieves a row by primary key |
127 | | PATCH | /api/tableName/:id | Updates row element by primary key |
128 | | DELETE | /api/tableName/:id | Delete a row by primary key |
129 | | GET | /api/tableName/findOne | Works as list but gets single record matching criteria |
130 | | GET | /api/tableName/count | Count number of rows in a table |
131 | | GET | /api/tableName/distinct | Distinct row(s) in table - /api/tableName/distinct?_fields=col1|
132 | | GET | /api/tableName/:id/exists | True or false whether a row exists or not |
133 | | GET | [/api/parentTable/:id/childTable](#relational-tables) | Get list of child table rows with parent table foreign key |
134 | | GET :fire:| [/api/tableName/aggregate](#aggregate-functions) | Aggregate results of numeric column(s) |
135 | | GET :fire:| [/api/tableName/groupby](#group-by-having-as-api) | Group by results of column(s) |
136 | | GET :fire:| [/api/tableName/ugroupby](#union-of-multiple-group-by-statements) | Multiple group by results using one call |
137 | | GET :fire:| [/api/tableName/chart](#chart) | Numeric column distribution based on (min,max,step) or(step array) or (automagic)|
138 | | GET :fire:| [/api/tableName/autochart](#autochart) | Same as Chart but identifies which are numeric column automatically - gift for lazy while prototyping|
139 | | GET :fire:| [/api/xjoin](#xjoin) | handles join |
140 | | GET :fire:| [/dynamic](#run-dynamic-queries) | execute dynamic mysql statements with params |
141 | | GET :fire:| [/upload](#upload-single-file) | upload single file |
142 | | GET :fire:| [/uploads](#upload-multiple-files) | upload multiple files |
143 | | GET :fire:| [/download](#download-file) | download a file |
144 | | GET | /api/tableName/describe | describe each table for its columns |
145 | | GET | /api/tables | get all tables in database |
146 | | GET | [/_health](#health) | gets health of process and mysql -- details query params for more details |
147 | | GET | [/_version](#version) | gets version of Xmysql, mysql, node|
148 |
149 |
150 | ## Relational Tables
151 | xmysql identifies foreign key relations automatically and provides GET api.
152 | ```
153 | /api/blogs/103/comments
154 | ```
155 | eg: blogs is parent table and comments is child table. API invocation will result in all comments for blog primary key 103.
156 | [:arrow_heading_up:](#api-overview)
157 |
158 |
159 | ## Support for composite primary keys
160 |
161 | #### ___ (three underscores)
162 |
163 | ```
164 | /api/payments/103___JM555205
165 | ```
166 | *___* : If there are multiple primary keys - separate them by three underscores as shown
167 |
168 | ## Pagination
169 |
170 | #### _p & _size
171 |
172 | _p indicates page and _size indicates size of response rows
173 |
174 | By default 20 records and max of 100 are returned per GET request on a table.
175 |
176 | ```
177 | /api/payments?_size=50
178 | ```
179 | ```
180 | /api/payments?_p=2
181 | ```
182 | ```
183 | /api/payments?_p=2&_size=50
184 | ```
185 |
186 | When _size is greater than 100 - number of records defaults to 100 (i.e maximum)
187 |
188 | When _size is less than or equal to 0 - number of records defaults to 20 (i.e minimum)
189 |
190 | ## Order by / Sorting
191 |
192 | #### ASC
193 |
194 | ```
195 | /api/payments?_sort=column1
196 | ```
197 | eg: sorts ascending by column1
198 |
199 | #### DESC
200 |
201 | ```
202 | /api/payments?_sort=-column1
203 | ```
204 | eg: sorts descending by column1
205 |
206 | #### Multiple fields in sort
207 |
208 | ```
209 | /api/payments?_sort=column1,-column2
210 | ```
211 | eg: sorts ascending by column1 and descending by column2
212 |
213 |
214 | ## Column filtering / Fields
215 | ```
216 | /api/payments?_fields=customerNumber,checkNumber
217 | ```
218 | eg: gets only customerNumber and checkNumber in response of each record
219 | ```
220 | /api/payments?_fields=-checkNumber
221 | ```
222 | eg: gets all fields in table row but not checkNumber
223 |
224 | ## Row filtering / Where
225 |
226 | #### Comparison operators
227 |
228 | ```
229 | eq - '=' - (colName,eq,colValue)
230 | ne - '!=' - (colName,ne,colValue)
231 | gt - '>' - (colName,gt,colValue)
232 | gte - '>=' - (colName,gte,colValue)
233 | lt - '<' - (colName,lt,colValue)
234 | lte - '<=' - (colName,lte,colValue)
235 | is - 'is' - (colName,is,true/false/null)
236 | in - 'in' - (colName,in,val1,val2,val3,val4)
237 | bw - 'between' - (colName,bw,val1,val2)
238 | like - 'like' - (colName,like,~name) note: use ~ in place of %
239 | nlike - 'not like' - (colName,nlike,~name) note: use ~ in place of %
240 | ```
241 |
242 | #### Use of comparison operators
243 | ```
244 | /api/payments?_where=(checkNumber,eq,JM555205)~or((amount,gt,200)~and(amount,lt,2000))
245 | ```
246 |
247 | #### Logical operators
248 | ```
249 | ~or - 'or'
250 | ~and - 'and'
251 | ~xor - 'xor'
252 | ```
253 |
254 | #### Use of logical operators
255 |
256 | eg: simple logical expression
257 | ```
258 | /api/payments?_where=(checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933)
259 | ```
260 |
261 | eg: complex logical expression
262 | ```
263 | /api/payments?_where=((checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933))~and(amount,gt,100)
264 | ```
265 |
266 | eg: logical expression with sorting(_sort), pagination(_p), column filtering (_fields)
267 | ```
268 | /api/payments?_where=(amount,gte,1000)&_sort=-amount&p=2&_fields=customerNumber
269 | ```
270 |
271 | eg: filter of rows using _where is available for relational route URLs too.
272 | ```
273 | /api/offices/1/employees?_where=(jobTitle,eq,Sales%20Rep)
274 | ```
275 |
276 | ## FindOne
277 | ```
278 | /api/tableName/findOne?_where=(id,eq,1)
279 | ```
280 | Works similar to list but only returns top/one result. Used in conjunction with _where
281 | [:arrow_heading_up:](#api-overview)
282 |
283 | ## Count
284 | ```
285 | /api/tableName/count
286 | ```
287 |
288 | Returns number of rows in table
289 | [:arrow_heading_up:](#api-overview)
290 |
291 | ## Exists
292 | ```
293 | /api/tableName/1/exists
294 | ```
295 |
296 | Returns true or false depending on whether record exists
297 | [:arrow_heading_up:](#api-overview)
298 |
299 | ## Group By Having as query params
300 | [:arrow_heading_up:](#api-overview)
301 |
302 | ```
303 | /api/offices?_groupby=country
304 | ```
305 | eg: SELECT country,count(*) FROM offices GROUP BY country
306 |
307 | ```
308 | /api/offices?_groupby=country&_having=(_count,gt,1)
309 | ```
310 | eg: SELECT country,count(1) as _count FROM offices GROUP BY country having _count > 1
311 |
312 |
313 | ## Group By Having as API
314 | [:arrow_heading_up:](#api-overview)
315 |
316 | ```
317 | /api/offices/groupby?_fields=country
318 | ```
319 | eg: SELECT country,count(*) FROM offices GROUP BY country
320 |
321 | ```
322 | /api/offices/groupby?_fields=country,city
323 | ```
324 | eg: SELECT country,city,count(*) FROM offices GROUP BY country,city
325 |
326 | ```
327 | /api/offices/groupby?_fields=country,city&_having=(_count,gt,1)
328 | ```
329 | eg: SELECT country,city,count(*) as _count FROM offices GROUP BY country,city having _count > 1
330 |
331 |
332 | ### Group By, Order By
333 | [:arrow_heading_up:](#api-overview)
334 |
335 | ```
336 | /api/offices/groupby?_fields=country,city&_sort=city
337 | ```
338 | eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC
339 |
340 | ```
341 | /api/offices/groupby?_fields=country,city&_sort=city,country
342 | ```
343 | eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC, country ASC
344 |
345 | ```
346 | /api/offices/groupby?_fields=country,city&_sort=city,-country
347 | ```
348 | eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC, country DESC
349 |
350 |
351 | ## Aggregate functions
352 | [:arrow_heading_up:](#api-overview)
353 |
354 | ```
355 | http://localhost:3000/api/payments/aggregate?_fields=amount
356 |
357 | response body
358 | [
359 | {
360 | "min_of_amount": 615.45,
361 | "max_of_amount": 120166.58,
362 | "avg_of_amount": 32431.645531,
363 | "sum_of_amount": 8853839.23,
364 | "stddev_of_amount": 20958.625377426568,
365 | "variance_of_amount": 439263977.71130896
366 | }
367 | ]
368 | ```
369 |
370 | eg: retrieves all numeric aggregate of a column in a table
371 |
372 | ```
373 | http://localhost:3000/api/orderDetails/aggregate?_fields=priceEach,quantityOrdered
374 |
375 | response body
376 | [
377 | {
378 | "min_of_priceEach": 26.55,
379 | "max_of_priceEach": 214.3,
380 | "avg_of_priceEach": 90.769499,
381 | "sum_of_priceEach": 271945.42,
382 | "stddev_of_priceEach": 36.576811252187795,
383 | "variance_of_priceEach": 1337.8631213781719,
384 | "min_of_quantityOrdered": 6,
385 | "max_of_quantityOrdered": 97,
386 | "avg_of_quantityOrdered": 35.219,
387 | "sum_of_quantityOrdered": 105516,
388 | "stddev_of_quantityOrdered": 9.832243813502942,
389 | "variance_of_quantityOrdered": 96.67301840816688
390 | }
391 | ]
392 | ```
393 |
394 | eg: retrieves numeric aggregate can be done for multiple columns too
395 |
396 | ## Union of multiple group by statements
397 | [:arrow_heading_up:](#api-overview)
398 |
399 | :fire::fire:**[ HOTNESS ALERT ]**
400 |
401 | Group by multiple columns in one API call using _fields query params - comes really handy
402 |
403 | ```
404 | http://localhost:3000/api/employees/ugroupby?_fields=jobTitle,reportsTo
405 |
406 | response body
407 | {
408 | "jobTitle":[
409 | {
410 | "Sales Rep":17
411 | },
412 | {
413 | "President":1
414 | },
415 | {
416 | "Sale Manager (EMEA)":1
417 | },
418 | {
419 | "Sales Manager (APAC)":1
420 | },
421 | {
422 | "Sales Manager (NA)":1
423 | },
424 | {
425 | "VP Marketing":1
426 | },
427 | {
428 | "VP Sales":1
429 | }
430 | ],
431 | "reportsTo":[
432 | {
433 | "1002":2
434 | },
435 | {
436 | "1056":4
437 | },
438 | {
439 | "1088":3
440 | },
441 | {
442 | "1102":6
443 | },
444 | {
445 | "1143":6
446 | },
447 | {
448 | "1621":1
449 | }
450 | {
451 | "":1
452 | },
453 | ]
454 | }
455 | ```
456 |
457 |
458 | ## Chart
459 | [:arrow_heading_up:](#api-overview)
460 |
461 | :fire::fire::fire::fire::fire::fire: **[ HOTNESS ALERT ]**
462 |
463 | Chart API returns distribution of a numeric column in a table
464 |
465 | It comes in **SEVEN** powerful flavours
466 |
467 | 1. Chart : With min, max, step in query params :fire::fire:
468 | [:arrow_heading_up:](#api-overview)
469 |
470 | This API returns the number of rows where amount is between (0,25000), (25001,50000) ...
471 |
472 | ```
473 | /api/payments/chart?_fields=amount&min=0&max=131000&step=25000
474 |
475 | Response
476 |
477 | [
478 | {
479 | "amount": "0 to 25000",
480 | "_count": 107
481 | },
482 | {
483 | "amount": "25001 to 50000",
484 | "_count": 124
485 | },
486 | {
487 | "amount": "50001 to 75000",
488 | "_count": 30
489 | },
490 | {
491 | "amount": "75001 to 100000",
492 | "_count": 7
493 | },
494 | {
495 | "amount": "100001 to 125000",
496 | "_count": 5
497 | },
498 | {
499 | "amount": "125001 to 150000",
500 | "_count": 0
501 | }
502 | ]
503 |
504 | ```
505 |
506 | 2. Chart : With step array in params :fire::fire:
507 | [:arrow_heading_up:](#api-overview)
508 |
509 | This API returns distribution between the step array specified
510 |
511 | ```
512 | /api/payments/chart?_fields=amount&steparray=0,10000,20000,70000,140000
513 |
514 | Response
515 |
516 | [
517 | {
518 | "amount": "0 to 10000",
519 | "_count": 42
520 | },
521 | {
522 | "amount": "10001 to 20000",
523 | "_count": 36
524 | },
525 | {
526 | "amount": "20001 to 70000",
527 | "_count": 183
528 | },
529 | {
530 | "amount": "70001 to 140000",
531 | "_count": 12
532 | }
533 | ]
534 |
535 |
536 | ```
537 |
538 | 3. Chart : With step pairs in params :fire::fire:
539 | [:arrow_heading_up:](#api-overview)
540 |
541 | This API returns distribution between each step pair
542 |
543 | ```
544 | /api/payments/chart?_fields=amount&steppair=0,50000,40000,100000
545 |
546 | Response
547 |
548 | [
549 | {"amount":"0 to 50000","_count":231},
550 | {"amount":"40000 to 100000","_count":80}
551 | ]
552 |
553 |
554 | ```
555 |
556 | 4. Chart : with no params :fire::fire:
557 | [:arrow_heading_up:](#api-overview)
558 |
559 | This API figures out even distribution of a numeric column in table and returns the data
560 |
561 | ```
562 | /api/payments/chart?_fields=amount
563 |
564 | Response
565 | [
566 | {
567 | "amount": "-9860 to 11100",
568 | "_count": 45
569 | },
570 | {
571 | "amount": "11101 to 32060",
572 | "_count": 91
573 | },
574 | {
575 | "amount": "32061 to 53020",
576 | "_count": 109
577 | },
578 | {
579 | "amount": "53021 to 73980",
580 | "_count": 16
581 | },
582 | {
583 | "amount": "73981 to 94940",
584 | "_count": 7
585 | },
586 | {
587 | "amount": "94941 to 115900",
588 | "_count": 3
589 | },
590 | {
591 | "amount": "115901 to 130650",
592 | "_count": 2
593 | }
594 | ]
595 |
596 | ```
597 |
598 | 5. Chart : range, min, max, step in query params :fire::fire:
599 | [:arrow_heading_up:](#api-overview)
600 |
601 | This API returns the number of rows where amount is between (0,25000), (0,50000) ... (0,maxValue)
602 |
603 | Number of records for amount is counted from min value to extended *Range* instead of incremental steps
604 |
605 | ```
606 | /api/payments/chart?_fields=amount&min=0&max=131000&step=25000&range=1
607 |
608 | Response
609 |
610 | [
611 | {
612 | "amount": "0 to 25000",
613 | "_count": 107
614 | },
615 | {
616 | "amount": "0 to 50000",
617 | "_count": 231
618 | },
619 | {
620 | "amount": "0 to 75000",
621 | "_count": 261
622 | },
623 | {
624 | "amount": "0 to 100000",
625 | "_count": 268
626 | },
627 | {
628 | "amount": "0 to 125000",
629 | "_count": 273
630 | }
631 | ]
632 |
633 | ```
634 |
635 | 6. Range can be specified with step array like below
636 |
637 | ```
638 | /api/payments/chart?_fields=amount&steparray=0,10000,20000,70000,140000&range=1
639 |
640 | [
641 | {
642 | "amount": "0 to 10000",
643 | "_count": 42
644 | },
645 | {
646 | "amount": "0 to 20000",
647 | "_count": 78
648 | },
649 | {
650 | "amount": "0 to 70000",
651 | "_count": 261
652 | },
653 | {
654 | "amount": "0 to 140000",
655 | "_count": 273
656 | }
657 | ]
658 | ```
659 |
660 | 7. Range can be specified without any step params like below
661 |
662 | ```
663 | /api/payments/chart?_fields=amount&range=1
664 |
665 | [
666 | {
667 | "amount": "-9860 to 11100",
668 | "_count": 45
669 | },
670 | {
671 | "amount": "-9860 to 32060",
672 | "_count": 136
673 | },
674 | ...
675 |
676 | ]
677 |
678 | ```
679 |
680 | Please Note:
681 | _fields in Chart API can only take numeric column as its argument.
682 |
683 | ## Autochart
684 |
685 | Identifies numeric columns in a table which are not any sort of key and applies chart API as before -
686 | feels like magic when there are multiple numeric columns in table while hacking/prototyping and you invoke this API.
687 |
688 | ```
689 | http://localhost:3000/api/payments/autochart
690 |
691 | [
692 | {
693 | "column": "amount",
694 | "chart": [
695 | {
696 | "amount": "-9860 to 11100",
697 | "_count": 45
698 | },
699 | {
700 | "amount": "11101 to 32060",
701 | "_count": 91
702 | },
703 | {
704 | "amount": "32061 to 53020",
705 | "_count": 109
706 | },
707 | {
708 | "amount": "53021 to 73980",
709 | "_count": 16
710 | },
711 | {
712 | "amount": "73981 to 94940",
713 | "_count": 7
714 | },
715 | {
716 | "amount": "94941 to 115900",
717 | "_count": 3
718 | },
719 | {
720 | "amount": "115901 to 130650",
721 | "_count": 2
722 | }
723 | ]
724 | }
725 | ]
726 | ```
727 |
728 | ## XJOIN
729 |
730 | ### Xjoin query params and values:
731 |
732 | ```
733 | _join : List of tableNames alternated by type of join to be made (_j, _ij,_ lj, _rj)
734 | alias.tableName : TableName as alias
735 | _j : Join [ _j => join, _ij => ij, _lj => left join , _rj => right join)
736 | _onNumber : Number 'n' indicates condition to be applied for 'n'th join between (n-1) and 'n'th table in list
737 | ```
738 |
739 | #### Simple example of two table join:
740 |
741 | Sql join query:
742 |
743 | ```sql
744 |
745 | SELECT pl.field1, pr.field2
746 | FROM productlines as pl
747 | JOIN products as pr
748 | ON pl.productline = pr.productline
749 |
750 | ```
751 |
752 | Equivalent xjoin query API:
753 | ```
754 | /api/xjoin?_join=pl.productlines,_j,pr.products&_on1=(pl.productline,eq,pr.productline)&_fields=pl.field1,pr.field2
755 | ```
756 |
757 | #### Multiple tables join
758 |
759 | Sql join query:
760 | ```sql
761 | SELECT pl.field1, pr.field2, ord.field3
762 | FROM productlines as pl
763 | JOIN products as pr
764 | ON pl.productline = pr.productline
765 | JOIN orderdetails as ord
766 | ON pr.productcode = ord.productcode
767 | ```
768 |
769 | Equivalent xjoin query API:
770 |
771 | ```
772 | /api/xjoin?_join=pl.productlines,_j,pr.products,_j,ord.orderDetails&_on1=(pl.productline,eq,pr.productline)&_on2=(pr.productcode,eq,ord.productcode)&_fields=pl.field1,pr.field2,ord.field3
773 |
774 | ```
775 |
776 | **Explanation:**
777 | > pl.productlines => productlines as pl
778 |
779 | > _j => join
780 |
781 | > pr.products => products as pl
782 |
783 | > _on1 => join condition between productlines and products => (pl.productline,eq,pr.productline)
784 |
785 | > _on2 => join condition between products and orderdetails => (pr.productcode,eq,ord.productcode)
786 |
787 | Example to use : _fields, _where, _p, _size in query params
788 |
789 | ```
790 | /api/xjoin?_join=pl.productlines,_j,pr.products&_on1=(pl.productline,eq,pr.productline)&_fields=pl.productline,pr.productName&_size=2&_where=(productName,like,1972~)
791 | ```
792 |
793 | Response:
794 |
795 | ```
796 | [{"pl_productline":"Classic Cars","pr_productName":"1972 Alfa Romeo GTA"}]
797 | ```
798 |
799 | Please note :
800 | Xjoin response has aliases for fields like below aliasTableName + '_' + columnName.
801 | eg: pl.productline in _fields query params - returns as pl_productline in response.
802 |
803 | ## Run dynamic queries
804 | [:arrow_heading_up:](#api-overview)
805 |
806 | Dynamic queries on a database can be run by POST method to URL localhost:3000/dynamic
807 |
808 | This is enabled **ONLY when using local mysql server** i.e -h localhost or -h 127.0.0.1 option.
809 |
810 | Post body takes two fields : query and params.
811 |
812 | >query: SQL query or SQL prepared query (ones with ?? and ?)
813 |
814 | >params : parameters for SQL prepared query
815 | ```
816 | POST /dynamic
817 |
818 | {
819 | "query": "select * from ?? limit 1,20",
820 | "params": ["customers"]
821 | }
822 | ```
823 |
824 | POST /dynamic URL can have any suffix to it - which can be helpful in prototyping
825 |
826 | eg:
827 |
828 | ```
829 | POST /dynamic/weeklyReport
830 | ```
831 |
832 | ```
833 | POST /dynamic/user/update
834 | ```
835 |
836 |
837 | ## Upload single file
838 | [:arrow_heading_up:](#api-overview)
839 |
840 | ```
841 | POST /upload
842 | ```
843 | Do POST operation on /upload url with multiform 'field' assigned to local file to be uploaded
844 |
845 | eg: curl --form file=@/Users/me/Desktop/a.png http://localhost:3000/upload
846 |
847 | returns uploaded file name else 'upload failed'
848 |
849 | (Note: POSTMAN has issues with file uploading hence examples with curl)
850 |
851 |
852 | ## Upload multiple files
853 | [:arrow_heading_up:](#api-overview)
854 |
855 | ```
856 | POST /uploads
857 | ```
858 | Do POST operation on /uploads url with multiform 'fields' assigned to local files to be uploaded
859 |
860 | > Notice 's' near /api/upload**s** and file**s** in below example
861 |
862 | eg: curl --form files=@/Users/me/Desktop/a.png --form files=@/Users/me/Desktop/b.png http://localhost:3000/uploads
863 |
864 | returns uploaded file names as string
865 |
866 | ## Download file
867 | [:arrow_heading_up:](#api-overview)
868 |
869 | http://localhost:3000/download?name=fileName
870 |
871 | > For upload and download of files -> you can specify storage folder using -s option
872 | > Upload and download apis are available only with local mysql server
873 |
874 | ## Health
875 | [:arrow_heading_up:](#api-overview)
876 |
877 | http://localhost:3000/_health
878 |
879 | ```
880 | {"process_uptime":3.858,"mysql_uptime":"2595"}
881 | ```
882 |
883 | Shows up time of Xmysql process and mysql server
884 |
885 |
886 | http://localhost:3000/_health?details=1
887 |
888 | ```
889 | {"process_uptime":1.151,"mysql_uptime":"2798",
890 | "os_total_memory":17179869184,
891 | "os_free_memory":2516357120,
892 | "os_load_average":[2.29931640625,2.1845703125,2.13818359375],
893 | "v8_heap_statistics":{"total_heap_size":24735744,
894 | "total_heap_size_executable":5242880,
895 | "total_physical_size":23521048,
896 | "total_available_size":1475503064,
897 | "used_heap_size":18149064,
898 | "heap_size_limit":1501560832,
899 | "malloced_memory":8192,
900 | "peak_malloced_memory":11065664,
901 | "does_zap_garbage":0}}
902 | ```
903 |
904 | Provides more details on process.
905 |
906 | Infact passing any query param gives detailed health output: example below
907 |
908 | http://localhost:3000/_health?voila
909 | ```
910 | {"process_uptime":107.793,"mysql_uptime":"2905","os_total_memory":17179869184,"os_free_memory":2573848576,"os_load_average":[2.052734375,2.12890625,2.11767578125],"v8_heap_statistics":{"total_heap_size":24735744,"total_heap_size_executable":5242880,"total_physical_size":23735016,"total_available_size":1475411128,"used_heap_size":18454968,"heap_size_limit":1501560832,"malloced_memory":8192,"peak_malloced_memory":11065664,"does_zap_garbage":0}}
911 | ```
912 |
913 | ## Version
914 | [:arrow_heading_up:](#api-overview)
915 |
916 | http://localhost:3000/_version
917 |
918 | ```
919 | {"Xmysql":"0.4.1","mysql":"5.7.15","node":"8.2.1"}
920 | ```
921 |
922 | ## When to use ?
923 | [:arrow_heading_up:](#api-overview)
924 |
925 | * You need just REST APIs for (ANY) MySql database at blink of an eye (literally).
926 | * You are learning new frontend frameworks and need REST APIs for your MySql database.
927 | * You are working on a demo, hacks etc
928 |
929 | ## When NOT to use ?
930 | [:arrow_heading_up:](#api-overview)
931 |
932 | * If you are in need of a full blown MVC framework, ACL, Validations, Authorisation etc - its early days please watch/star this repo to keep a tab on progress.
933 |
934 |
935 | ### Command line options
936 | [:arrow_heading_up:](#api-overview)
937 |
938 | ```
939 | Options:
940 |
941 | -V, --version Output the version number
942 | -h, --host Hostname of database -> localhost by default
943 | -u, --user Username of database -> root by default
944 | -p, --password Password of database -> empty by default
945 | -d, --database database schema name
946 | -r, --ipAddress IP interface of your server / localhost by default
947 | -n, --portNumber Port number for app -> 3000 by default
948 | -o, --port Port number of mysql -> 3306 by default
949 | -a, --apiPrefix Api url prefix -> /api/ by default
950 | -s, --storageFolder Storage folder -> current working dir by default (available only with local)
951 | -i, --ignoreTables Comma separated table names to ignore
952 | -c, --useCpuCores Specify number of cpu cores to use / 1 by default / 0 to use max
953 | -y, --readOnly readonly apis -> false by default
954 | -h, --help Output usage information
955 |
956 |
957 | Examples:
958 |
959 | $ xmysql -u username -p password -d databaseSchema
960 | ```
961 |
962 |
963 |
Boost Your Hacker Karma By Sharing :
964 |

965 |

966 |

967 |

968 |

969 |

970 |

971 |

972 |

973 |

974 |
975 |
976 |
977 |
978 |
979 | # Docker
980 | [:arrow_heading_up:](#features)
981 |
982 | Simply run with `docker run -p 3000:80 -d markuman/xmysql:0.4.2`
983 |
984 | The best way for testing is to run mysql in a docker container too and create a docker network, so that `xmysql` can access the `mysql` container with a name from docker network.
985 |
986 | 1. Create network
987 | * `docker network create mynet`
988 | 2. Start mysql with docker name `some-mysql` and bind to docker network `mynet`
989 | * `docker run --name some-mysql -p 3306:3306 --net mynet -e MYSQL_ROOT_PASSWORD=password -d markuman/mysql`
990 | 3. run xmysql and set env variable for `some-mysql` from step 2
991 | * `docker run -p 3000:80 -d -e DATABASE_HOST=some-mysql --net mynet markuman/xmysql`
992 |
993 | You can also pass the environment variables to a file and use them as an option with docker like `docker run --env-file ./env.list -p 3000:80 --net mynet -d markuman/xmysql`
994 |
995 | environment variables which can be used:
996 |
997 | ```
998 | ENV DATABASE_HOST 127.0.0.1
999 | ENV DATABASE_USER root
1000 | ENV DATABASE_PASSWORD password
1001 | ENV DATABASE_NAME sakila
1002 | ```
1003 |
1004 | Furthermore, the docker container of xmysql is listen on port 80. You can than request it just with `http://xmysql/api/` in other services running in the same docker network.
1005 |
1006 | ## Debugging xmysql in docker.
1007 |
1008 | Given you've deployed your xmysql docker container like
1009 |
1010 | ```shell
1011 | docker run -d \
1012 | --network local_dev \
1013 | --name xmysql \
1014 | -p 3000:80 \
1015 | -e DATABASE_HOST=mysql_host \
1016 | -e DATABASE_USER=root \
1017 | -e DATABASE_PASSWORD=password \
1018 | -e DATABASE_NAME=sys \
1019 | markuman/xmysql:0.4.2
1020 | ```
1021 |
1022 | but the response is just
1023 |
1024 | ```json
1025 | ["http://127.0.0.1:3000/api/tables","http://127.0.0.1:3000/api/xjoin"]
1026 | ```
1027 |
1028 | then obviously the connection to your mysql database failed.
1029 |
1030 | 1. attache to the xmysql image
1031 | * `docker exec -ti xmysql`
1032 | 2. install mysql cli client
1033 | * `apk --update --no-cache add mysql-client`
1034 | 3. try to access your mysql database
1035 | * `mysql-client -h mysql_host`
1036 | 4. profit from the `mysql-client` error output and improve the environment variables for mysql
1037 |
1038 | # Nginx Reverse Proxy Config with Docker
1039 | [:arrow_heading_up:](#features)
1040 |
1041 | This is a config example when you use nginx as reverse proxy
1042 |
1043 | ```
1044 | events {
1045 | worker_connections 1024;
1046 |
1047 | }
1048 | http {
1049 | server {
1050 | server_name 127.0.0.1;
1051 | listen 80 ;
1052 | location / {
1053 | rewrite ^/(.*) /$1 break;
1054 | proxy_redirect off;
1055 | proxy_set_header Host $host;
1056 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
1057 | proxy_pass http://127.0.0.1:3000;
1058 | }
1059 | }
1060 | }
1061 | ```
1062 |
1063 | e.g.
1064 |
1065 | 0. create a docker network `docker network create local_dev`
1066 | 1. start a mysql server `docker run -d --name mysql -p 3306:3306 --network local_dev -e MYSQL_ROOT_PASSWORD=password mysql`
1067 | 2. start xmysql `docker run -d --network local_dev --name xmyxql -e DATABASE_NAME=sys -e DATABASE_HOST=mysql -p 3000:80 markuman/xmysql:0.4.2`
1068 | 3. start nginx on host system with the config above `sudo nginx -g 'daemon off;' -c /tmp/nginx.conf`
1069 | 4. profit `curl http://127.0.0.1/api/host_summary_by_file_io_type/describe`
1070 |
1071 | When you start your nginx proxy in a docker container too, use as `proxy_pass` the `--name` value of xmysql. E.g. `proxy_pass http://xmysql` (remember, xmysql runs in it's docker container already on port 80).
1072 |
1073 |
1074 | # Tests : setup on local machine
1075 | [:arrow_heading_up:](#api-overview)
1076 | ```
1077 | docker-compose run test
1078 | ```
1079 | * Requires `docker-compose` to be installed on your machine.
1080 |
--------------------------------------------------------------------------------
/assets/doubon copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/doubon copy.png
--------------------------------------------------------------------------------
/assets/doubon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/doubon.png
--------------------------------------------------------------------------------
/assets/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/facebook.png
--------------------------------------------------------------------------------
/assets/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/google.png
--------------------------------------------------------------------------------
/assets/hn.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/hn.jpg
--------------------------------------------------------------------------------
/assets/hn1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/hn1.png
--------------------------------------------------------------------------------
/assets/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/linkedin.png
--------------------------------------------------------------------------------
/assets/log.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/log.gif
--------------------------------------------------------------------------------
/assets/out.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/out.gif
--------------------------------------------------------------------------------
/assets/pinterest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/pinterest.png
--------------------------------------------------------------------------------
/assets/reddit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/reddit.png
--------------------------------------------------------------------------------
/assets/renren.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/renren.png
--------------------------------------------------------------------------------
/assets/rick-and-morty.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/rick-and-morty.gif
--------------------------------------------------------------------------------
/assets/stumbleupon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/stumbleupon.png
--------------------------------------------------------------------------------
/assets/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/twitter.png
--------------------------------------------------------------------------------
/assets/vk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/vk.png
--------------------------------------------------------------------------------
/assets/weibo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/weibo.png
--------------------------------------------------------------------------------
/assets/wykop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o1lab/xmysql/8c6b00ee22860230975e43ab705d015d2235e308/assets/wykop.png
--------------------------------------------------------------------------------
/bin/index.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | const morgan = require("morgan");
4 | const bodyParser = require("body-parser");
5 | const express = require("express");
6 | const sqlConfig = require("commander");
7 | const mysql = require("mysql");
8 | const cors = require("cors");
9 | const dataHelp = require("../lib/util/data.helper.js");
10 | const Xapi = require("../lib/xapi.js");
11 | const cmdargs = require("../lib/util/cmd.helper.js");
12 | const cluster = require("cluster");
13 | const numCPUs = require("os").cpus().length;
14 |
15 |
16 | function startXmysql(sqlConfig) {
17 | /**************** START : setup express ****************/
18 | let app = express();
19 | app.use(morgan("tiny"));
20 | app.use(cors());
21 | app.use(bodyParser.json());
22 | app.use(
23 | bodyParser.urlencoded({
24 | extended: true
25 | })
26 | );
27 | /**************** END : setup express ****************/
28 |
29 | /**************** START : setup mysql ****************/
30 | let mysqlPool = mysql.createPool(sqlConfig);
31 | /**************** END : setup mysql ****************/
32 |
33 | /**************** START : setup Xapi ****************/
34 | console.log("");
35 | console.log("");
36 | console.log("");
37 | console.log(" Generating REST APIs at the speed of your thought.. ");
38 | console.log("");
39 |
40 | let t = process.hrtime();
41 | let moreApis = new Xapi(sqlConfig, mysqlPool, app);
42 |
43 | moreApis.init((err, results) => {
44 | app.listen(sqlConfig.portNumber, sqlConfig.ipAddress);
45 | var t1 = process.hrtime(t);
46 | var t2 = t1[0] + t1[1] / 1000000000;
47 |
48 | console.log(
49 | " Xmysql took : %d seconds",
50 | dataHelp.round(t2, 1)
51 | );
52 | console.log(
53 | " API's base URL : " +
54 | "localhost:" +
55 | sqlConfig.portNumber
56 | );
57 | console.log(" ");
58 | console.log(
59 | " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "
60 | );
61 | });
62 | /**************** END : setup Xapi ****************/
63 | }
64 |
65 | function start(sqlConfig) {
66 | //handle cmd line arguments
67 | cmdargs.handle(sqlConfig);
68 |
69 | if (cluster.isMaster && sqlConfig.useCpuCores > 1) {
70 | console.log(`Master ${process.pid} is running`);
71 |
72 | for (let i = 0; i < numCPUs && i < sqlConfig.useCpuCores; i++) {
73 | console.log(`Forking process number ${i}...`);
74 | cluster.fork();
75 | }
76 |
77 | cluster.on("exit", function(worker, code, signal) {
78 | console.log(
79 | "Worker " +
80 | worker.process.pid +
81 | " died with code: " +
82 | code +
83 | ", and signal: " +
84 | signal
85 | );
86 | console.log("Starting a new worker");
87 | cluster.fork();
88 | });
89 | } else {
90 | startXmysql(sqlConfig);
91 | }
92 | }
93 |
94 | start(sqlConfig);
95 |
--------------------------------------------------------------------------------
/dev.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM frolvlad/alpine-python2
2 |
3 | RUN apk --update --no-cache add \
4 | g++ \
5 | make \
6 | nodejs \
7 | nodejs-npm \
8 | paxctl \
9 | && paxctl -cm $(which node)
10 |
11 | WORKDIR /usr/src/app
12 |
13 | COPY package.json .
14 | RUN npm install
15 |
16 | COPY . .
17 |
18 | ENTRYPOINT ["./docker-entrypoint.sh"]
19 |
20 | CMD ["sh", "-c", "node index.js -h $DATABASE_HOST -p $DATABASE_PASSWORD -d $DATABASE_NAME -u $DATABASE_USER -n 80 -r 0.0.0.0"]
21 |
22 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.5'
2 |
3 |
4 | services:
5 | db:
6 | image: mysql:5.7
7 | restart: always
8 | command: --init-file /docker-entrypoint-initdb.d/sample.sql
9 | environment:
10 | MYSQL_ROOT_PASSWORD: 'pass'
11 | MYSQL_USER: 'root'
12 | MYSQL_PASSWORD: 'pass'
13 | MYSQL_DATABASE: 'classicmodels'
14 | volumes:
15 | - ./tests/docker-sample.sql:/docker-entrypoint-initdb.d/sample.sql
16 | - db-data:/var/lib/mysql
17 | ports:
18 | - "3306:3306"
19 |
20 | db-api: &db-api
21 | build:
22 | context: ./
23 | dockerfile: ./dev.Dockerfile
24 | environment:
25 | DATABASE_HOST: 'db'
26 | DATABASE_USER: 'root'
27 | DATABASE_PASSWORD: 'pass'
28 | DATABASE_NAME: 'classicmodels'
29 | ports:
30 | - "3002:80"
31 | depends_on:
32 | - db
33 |
34 | test:
35 | <<: *db-api
36 | command: sh -c "npm test"
37 | volumes:
38 | - ./tests:/usr/src/app/tests
39 | - ./lib:/usr/src/app/lib
40 |
41 | volumes:
42 | db-data:
43 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # https://stackoverflow.com/questions/25503412/how-do-i-know-when-my-docker-mysql-container-is-up-and-mysql-is-ready-for-taking
3 |
4 |
5 | set -e
6 |
7 | until nc -z -v -w30 $DATABASE_HOST 3306
8 | do
9 | echo "Waiting for database connection..."
10 | # wait for 5 seconds before check again
11 | sleep 5
12 | done
13 |
14 | echo "Mysql is up - executing command"
15 |
16 | exec "$@"
17 |
--------------------------------------------------------------------------------
/dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.7
2 |
3 |
4 | RUN apk --update --no-cache add \
5 | nodejs \
6 | nodejs-npm
7 |
8 | # Bug fix for segfault ( Convert PT_GNU_STACK program header into PT_PAX_FLAGS )
9 | RUN apk --update --no-cache add paxctl \
10 | && paxctl -cm $(which node)
11 |
12 | RUN mkdir -p /usr/src/{app,bin,lib}
13 | WORKDIR /usr/src/app
14 |
15 | # only install production deps to keep image small
16 | COPY package.json /usr/src/app
17 | RUN npm install --production
18 |
19 | RUN apk del nodejs-npm
20 |
21 | COPY index.js /usr/src/app
22 | COPY bin/ /usr/src/app/bin
23 | COPY lib/ /usr/src/app/lib
24 | COPY docker-entrypoint.sh /docker-entrypoint.sh
25 |
26 | # env
27 | ENV DATABASE_HOST 127.0.0.1
28 | ENV DATABASE_USER root
29 | ENV DATABASE_PASSWORD password
30 | ENV DATABASE_NAME sakila
31 |
32 | EXPOSE 80
33 | ENTRYPOINT ["/docker-entrypoint.sh"]
34 |
--------------------------------------------------------------------------------
/examples/aws-lambda/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * I was able to get xmysql to work on AWS lambda. This is just some docs on the process to help others.
3 | * AWS setup notes:
4 | * * The deploy uses serverless: https://serverless.com/framework/docs/getting-started/
5 | * * The lambda function is accessed through API Gateway and access the RDS (mysql) database
6 | * ** Make sure you deploy your API Gateway setup otherwise it won't be accessible.
7 | * * The security setup:
8 | * ** You need to put your lambda in a VPC
9 | * ** The lambda role needs access to the VPC
10 | * ** The RDS server has to be inside the VPC as well
11 | *
12 | * Performance:
13 | * * Requests to the API Gateway resolve in ~ 90ms
14 | *
15 | * Extra packages needed:
16 | * * serverless-http
17 | *
18 | * More info on deploying this to lambda:
19 | * * https://serverless.com/blog/serverless-express-rest-api/
20 | */
21 |
22 |
23 | "use strict";
24 | Object.defineProperty(exports, "__esModule", { value: true });
25 | var bodyParser = require("body-parser");
26 | var express = require("express");
27 | var serverless = require("serverless-http");
28 | var cors = require("cors");
29 | var mysql = require("mysql");
30 | var Xapi = require("./node_modules/xmysql/lib/xapi.js");
31 | var morgan = require("morgan");
32 | var app = express();
33 |
34 | var onXapiInitialized = new Promise(function(resolve, reject) {
35 | try {
36 | // /**************** START : setup express ****************/
37 | app.use(morgan("tiny"));
38 | app.use(cors());
39 | app.use(bodyParser.json());
40 | app.use(
41 | bodyParser.urlencoded({
42 | extended: true
43 | })
44 | );
45 | // /**************** END : setup express ****************/
46 |
47 | app.use(function(req, res, next) {
48 | // You can add authentication here
49 | console.log("Received request for: " + req.url, req);
50 | next();
51 | });
52 |
53 | var mysqlConfig = {
54 | host: config.mysql.host,
55 | port: 3306,
56 | database: config.mysql.database,
57 | user: config.mysql.user,
58 | password: config.mysql.password,
59 | apiPrefix: "/",
60 | ipAddress: "localhost",
61 | portNumber: 3000,
62 | ignoreTables: [],
63 | storageFolder: __dirname
64 | };
65 |
66 | var mysqlPool = mysql.createPool(mysqlConfig);
67 | var xapi = new Xapi(mysqlConfig, mysqlPool, app);
68 | xapi.init(function(err, results) {
69 | app.listen(3000);
70 | resolve();
71 | });
72 | } catch (err) {
73 | reject(err);
74 | }
75 | });
76 |
77 | function handler(event, context, callback) {
78 | onXapiInitialized.then(function() {
79 | serverless(app)(event, context, callback);
80 | });
81 | }
82 |
83 | exports.handler = handler;
84 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | exports.xapi = require('./lib/xapi.js')
2 |
--------------------------------------------------------------------------------
/lib/util/cmd.helper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const program = require("commander");
3 | const colors = require("colors");
4 | const maxCpus = require("os").cpus().length;
5 |
6 | program.on("--help", () => {
7 | console.log("");
8 | console.log(" Examples:".blue);
9 | console.log("");
10 | console.log(" $ xmysql -u username -p password -d databaseSchema".blue);
11 | console.log("");
12 | });
13 |
14 | program
15 | .version(module.exports.version)
16 | .option("-h, --host ", "hostname of database / localhost by default")
17 | .option("-u, --user ", "username of database / root by default")
18 | .option("-p, --password ", "password of database / empty by default")
19 | .option("-d, --database ", "database schema name")
20 | .option(
21 | "-r, --ipAddress ",
22 | "IP interface of your server / localhost by default"
23 | )
24 | .option("-n, --portNumber ", "port number for app / 3000 by default")
25 | .option("-o, --port ", "port number for mysql / 3306 by default")
26 | .option("-S, --socketPath ", "unix socket path / not used by default")
27 | .option(
28 | "-s, --storageFolder ",
29 | "storage folder / current working dir by default / available only with local"
30 | )
31 | .option("-i, --ignoreTables ", "comma separated table names to ignore")
32 | .option("-a, --apiPrefix ", 'api url prefix / "/api/" by default')
33 | .option("-y, --readOnly", "readonly apis / false by default")
34 | .option(
35 | "-c, --useCpuCores ",
36 | "use number of CPU cores (using cluster) / 1 by default"
37 | )
38 | .parse(process.argv);
39 |
40 | function paintHelp(txt) {
41 | return colors.magenta(txt); //display the help text in a color
42 | }
43 |
44 | function processInvalidArguments(program) {
45 | let err = "";
46 |
47 | if (!program.password) {
48 | err += "Error: password for database is missing\n";
49 | }
50 |
51 | if (!program.database) {
52 | err += "Error: database name is missing\n";
53 | }
54 |
55 | if (err !== "") {
56 | program.outputHelp(paintHelp);
57 | console.log(err.red);
58 | }
59 | }
60 |
61 | exports.handle = program => {
62 | /**************** START : default values ****************/
63 | program.ipAddress = program.ipAddress || "localhost";
64 | program.portNumber = program.portNumber || 3000;
65 | program.port = program.port || 3306;
66 | program.user = program.user || "root";
67 | program.password = program.password || "";
68 | program.host = program.host || "localhost";
69 | program.socketPath = program.socketPath || "";
70 | program.storageFolder = program.storageFolder || process.cwd();
71 | program.apiPrefix = program.apiPrefix || "/api/";
72 | program.readOnly = program.readOnly || false;
73 | program.useCpuCores = program.useCpuCores || 1;
74 |
75 | if (program.useCpuCores === "0") {
76 | program.useCpuCores = maxCpus;
77 | }
78 |
79 | if (program.ignoreTables) {
80 | let ignoreTables = program.ignoreTables.split(",");
81 | program.ignoreTables = {};
82 | for (var i = 0; i < ignoreTables.length; ++i) {
83 | program.ignoreTables[ignoreTables[i]] = ignoreTables[i];
84 | }
85 | } else {
86 | program.ignoreTables = {};
87 | }
88 |
89 | program.connectionLimit = 10;
90 |
91 | if (
92 | program.host === "localhost" ||
93 | program.host === "127.0.0.1" ||
94 | program.host === "::1"
95 | ) {
96 | program.dynamic = 1;
97 | }
98 | // console.log(program);
99 | /**************** END : default values ****************/
100 |
101 | if (program.database && program.host && program.user) {
102 | //console.log('Starting server at:', 'http://' + program.host + ':' + program.portNumber)
103 | } else {
104 | processInvalidArguments(program);
105 | process.exit(1);
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/lib/util/data.helper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | exports.findOrInsertObjectArrayByKey = (obj, key, array) => {
4 | let found = 0;
5 | let i = 0;
6 |
7 | for (i = 0; i < array.length; ++i) {
8 | if (key in array[i]) {
9 | if (obj[key] === array[i][key]) {
10 | found = 1;
11 | break;
12 | }
13 | }
14 | }
15 |
16 | if (!found) {
17 | array.push(obj);
18 | }
19 |
20 | return array[i];
21 | };
22 |
23 | exports.findObjectInArrayByKey = (key, value, objArray) => {
24 | for (let i = 0; i < objArray.length; ++i) {
25 | if (objArray[i][key] === value) {
26 | return objArray[i];
27 | }
28 | }
29 |
30 | return null;
31 | };
32 |
33 | exports.round = function(number, precision) {
34 | var factor = Math.pow(10, precision);
35 | var tempNumber = number * factor;
36 | var roundedTempNumber = Math.round(tempNumber);
37 | return roundedTempNumber / factor;
38 | };
39 |
40 | exports.numberRound = (number, precision) => {
41 | var factor = Math.pow(10, precision);
42 | var tempNumber = number * factor;
43 | var roundedTempNumber = Math.round(tempNumber);
44 | return roundedTempNumber / factor;
45 | };
46 |
47 | exports.numberGetLength = number => {
48 | var n = number;
49 |
50 | if (number < 0) {
51 | n = n * -1;
52 | }
53 |
54 | return n.toString().length;
55 | };
56 |
57 | exports.numberGetFixed = number => {
58 | //console.log(number, typeof number);
59 | return parseInt(number.toFixed());
60 | };
61 |
62 | exports.getStepArraySimple = function(min, max, step) {
63 | var arr = [];
64 | for (var i = min; i <= max; i = i + step) {
65 | arr.push(i);
66 | }
67 |
68 | return arr;
69 | };
70 |
71 | exports.getStepArray = (min, max, stddev) => {
72 | // console.log(' = = = = = = = ');
73 | //console.log('original numbers', min, max, stddev);
74 |
75 | min = this.numberGetFixed(min);
76 | max = this.numberGetFixed(max);
77 | stddev = this.numberGetFixed(stddev);
78 |
79 | // console.log('fixed numbers', min, max, stddev);
80 |
81 | let minMinusHalf = min - stddev / 2;
82 | let maxMinusHalf = max + stddev / 2;
83 |
84 | minMinusHalf = this.numberGetFixed(minMinusHalf);
85 | maxMinusHalf = this.numberGetFixed(maxMinusHalf);
86 |
87 | // console.log('fixed numbers + (min,max)', min, max, stddev, '(', minMinusHalf, ',', maxMinusHalf, ')');
88 | // console.log('numbers length', 'min', numberGetLength(min), 'max', numberGetLength(max), 'stddev', numberGetLength(stddev));
89 |
90 | let minLen = this.numberGetLength(minMinusHalf);
91 | let maxLen = this.numberGetLength(maxMinusHalf);
92 | let stddevLen = this.numberGetLength(stddev);
93 | //
94 | // console.log('- - - -');
95 | // console.log('Range', 'min', numberRound(minMinusHalf, -1));
96 | // console.log('Range', 'max', numberRound(maxMinusHalf, -1));
97 | // console.log('Range', 'stddev', numberRound(stddev, -1));
98 |
99 | if (minLen > 1) minMinusHalf = this.numberRound(minMinusHalf, -1);
100 |
101 | if (maxLen > 2) maxMinusHalf = this.numberRound(maxMinusHalf, -1);
102 |
103 | if (stddevLen !== 1) stddev = this.numberRound(stddev, -1);
104 |
105 | var arr = [];
106 | for (var step = minMinusHalf; step < maxMinusHalf; step = step + stddev) {
107 | arr.push(step);
108 | }
109 | arr.push(maxMinusHalf);
110 |
111 | // console.log(arr);
112 |
113 | return arr;
114 | };
115 |
116 | exports.getSchemaQuery = function() {
117 | return (
118 | "select c.table_name, c.column_name, c.ordinal_position,c.column_key,c.is_nullable, c.data_type, c.column_type,c.extra,c.privileges, " +
119 | "c.column_comment,c.column_default,c.data_type,c.character_maximum_length, " +
120 | "k.constraint_name, k.referenced_table_name, k.referenced_column_name, " +
121 | "s.index_name,s.seq_in_index, " +
122 | "v.table_name as isView " +
123 | "from " +
124 | "information_schema.columns as c " +
125 | "left join " +
126 | "information_schema.key_column_usage as k " +
127 | "on " +
128 | "c.column_name=k.column_name and " +
129 | "c.table_schema = k.referenced_table_schema and " +
130 | "c.table_name = k.table_name " +
131 | "left join " +
132 | "information_schema.statistics as s " +
133 | "on " +
134 | "c.column_name = s.column_name and " +
135 | "c.table_schema = s.index_schema and " +
136 | "c.table_name = s.table_name " +
137 | "LEFT JOIN " +
138 | "information_schema.VIEWS as v " +
139 | "ON " +
140 | "c.table_schema = v.table_schema and " +
141 | "c.table_name = v.table_name " +
142 | "where " +
143 | "c.table_schema=? " +
144 | "order by " +
145 | "c.table_name, c.ordinal_position"
146 | );
147 | };
148 |
149 | exports.getRoutines = function () {
150 | return 'select routine_name from information_schema.routines'
151 | }
152 |
153 | exports.getChartQuery = function() {
154 | return "select ? as ??, count(*) as _count from ?? where ?? between ? and ? ";
155 | };
156 |
157 | exports.getDataType = function(colType, typesArr) {
158 | // console.log(colType,typesArr);
159 | for (let i = 0; i < typesArr.length; ++i) {
160 | if (colType.indexOf(typesArr[i]) !== -1) {
161 | return 1;
162 | }
163 | }
164 | return 0;
165 | };
166 |
167 | exports.getColumnType = function(column) {
168 | let strTypes = [
169 | "varchar",
170 | "text",
171 | "char",
172 | "tinytext",
173 | "mediumtext",
174 | "longtext",
175 | "blob",
176 | "mediumblob",
177 | "longblob",
178 | "tinyblob",
179 | "binary",
180 | "varbinary"
181 | ];
182 | let intTypes = ["int", "long", "smallint", "mediumint", "bigint", "tinyint"];
183 | let flatTypes = ["float", "double", "decimal"];
184 | let dateTypes = ["date", "datetime", "timestamp", "time", "year"];
185 |
186 | //console.log(column);
187 | if (this.getDataType(column["data_type"], strTypes)) {
188 | return "string";
189 | } else if (this.getDataType(column["data_type"], intTypes)) {
190 | return "int";
191 | } else if (this.getDataType(column["data_type"], flatTypes)) {
192 | return "float";
193 | } else if (this.getDataType(column["data_type"], dateTypes)) {
194 | return "date";
195 | } else {
196 | return "string";
197 | }
198 | };
199 |
200 | exports.getType = function(colType, typesArr) {
201 | for (let i = 0; i < typesArr.length; ++i) {
202 | // if (typesArr[i].indexOf(colType) !== -1) {
203 | // return 1;
204 | // }
205 |
206 | if (colType.indexOf(typesArr[i]) !== -1) {
207 | return 1;
208 | }
209 | }
210 | return 0;
211 | };
212 |
--------------------------------------------------------------------------------
/lib/util/whereClause.helper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | *
5 | * @param input : 1,2,3,4
6 | * @returns obj.query = (?,?,?,?) obj.params = [1,2,3,4]
7 | */
8 | function prepareInClauseParams(input) {
9 | let inElems = input.split(",");
10 | let obj = {};
11 | obj.whereQuery = "";
12 | obj.whereParams = [];
13 |
14 | for (var j = 0; j < inElems.length; ++j) {
15 | if (j === 0) {
16 | //enclose with open parenthesis
17 | obj.whereQuery += "(";
18 | }
19 | if (j) {
20 | obj.whereQuery += ",";
21 | }
22 | // add q mark and push the variable
23 | obj.whereQuery += "?";
24 | obj.whereParams.push(inElems[j]);
25 |
26 | if (j === inElems.length - 1) {
27 | //enclose with closing parenthesis
28 | obj.whereQuery += ")";
29 | }
30 | }
31 |
32 | //console.log(obj);
33 |
34 | return obj;
35 | }
36 |
37 | function prepareLikeValue(value) {
38 | //return value.replace("~", "%");
39 | return value.replace(/~/g, "%");
40 | }
41 |
42 | function prepareIsValue(value) {
43 | //return value.replace("~", "%");
44 | if (value === "null") {
45 | return null;
46 | } else if (value === "true") {
47 | return true;
48 | } else if (value === "false") {
49 | return false;
50 | } else {
51 | return null;
52 | }
53 | }
54 |
55 | function prepareBetweenValue(value) {
56 | let values = value.split(",");
57 | let obj = {};
58 | obj.whereQuery = "";
59 | obj.whereParams = [];
60 |
61 | if (values.length === 2) {
62 | obj.whereQuery = " ? and ? ";
63 | obj.whereParams.push(values[0]);
64 | obj.whereParams.push(values[1]);
65 | }
66 |
67 | //console.log('prepareBetweenValue', obj);
68 |
69 | return obj;
70 | }
71 |
72 | function replaceWhereParamsToQMarks(
73 | openParentheses,
74 | str,
75 | comparisonOperator,
76 | condType
77 | ) {
78 | let converted = "";
79 |
80 | if (openParentheses) {
81 | for (var i = 0; i < str.length; ++i) {
82 | if (str[i] === "(") {
83 | converted += "(";
84 | } else {
85 | converted += "??";
86 | break;
87 | }
88 | }
89 | } else {
90 | if (
91 | comparisonOperator !== " in " &&
92 | comparisonOperator !== " between " &&
93 | condType !== " on "
94 | ) {
95 | converted = "?";
96 | } else if (condType === " on ") {
97 | converted = "??";
98 | }
99 |
100 | for (var i = str.length - 1; i >= 0; --i) {
101 | if (str[i] === ")") {
102 | converted += ")";
103 | } else {
104 | break;
105 | }
106 | }
107 | }
108 |
109 | return converted;
110 | }
111 |
112 | function getComparisonOperator(operator) {
113 | switch (operator) {
114 | case "eq":
115 | return "=";
116 | break;
117 |
118 | case "ne":
119 | return "!=";
120 | break;
121 |
122 | case "lt":
123 | return "<";
124 | break;
125 |
126 | case "lte":
127 | return "<=";
128 | break;
129 |
130 | case "gt":
131 | return ">";
132 | break;
133 |
134 | case "gte":
135 | return ">=";
136 | break;
137 |
138 | case "is":
139 | return " is ";
140 | break;
141 |
142 | case "isnot":
143 | return " is not ";
144 | break;
145 |
146 | // case 'isnull':
147 | // return ' is NULL '
148 | // break;
149 |
150 | // case 'isnnull':
151 | // return ' is not NULL '
152 | // break;
153 |
154 | case "like":
155 | return " like ";
156 | break;
157 |
158 | case "nlike":
159 | return " not like ";
160 | break;
161 |
162 | case "in":
163 | return " in ";
164 | break;
165 |
166 | case "bw":
167 | return " between ";
168 | break;
169 |
170 | default:
171 | return null;
172 | break;
173 | }
174 | }
175 |
176 | function getLogicalOperator(operator) {
177 | switch (operator) {
178 | case "~or":
179 | return "or";
180 | break;
181 |
182 | case "~and":
183 | return "and";
184 | break;
185 |
186 | // case '~not':
187 | // return 'not'
188 | // break;
189 |
190 | case "~xor":
191 | return "xor";
192 | break;
193 |
194 | default:
195 | return null;
196 | break;
197 | }
198 | }
199 |
200 | exports.getConditionClause = function(whereInQueryParams, condType = "where") {
201 | let whereQuery = "";
202 | let whereParams = [];
203 | let grammarErr = 0;
204 | let numOfConditions = whereInQueryParams.split(/~or|~and|~not|~xor/);
205 | let logicalOperatorsInClause = whereInQueryParams.match(
206 | /(~or|~and|~not|~xor)/g
207 | );
208 |
209 | if (
210 | numOfConditions &&
211 | logicalOperatorsInClause &&
212 | numOfConditions.length !== logicalOperatorsInClause.length + 1
213 | ) {
214 | console.log(
215 | "conditions and logical operators mismatch",
216 | numOfConditions.length,
217 | logicalOperatorsInClause.length
218 | );
219 | } else {
220 | //console.log('numOfConditions',numOfConditions,whereInQueryParams);
221 | //console.log('logicalOperatorsInClause',logicalOperatorsInClause);
222 |
223 | for (var i = 0; i < numOfConditions.length; ++i) {
224 | let variable = "";
225 | let comparisonOperator = "";
226 | let logicalOperator = "";
227 | let variableValue = "";
228 | let temp = "";
229 |
230 | // split on commas
231 | var arr = numOfConditions[i].split(",");
232 |
233 | // consider first two splits only
234 | var result = arr.splice(0, 2);
235 |
236 | // join to make only 3 array elements
237 | result.push(arr.join(","));
238 |
239 | // variable, operator, variablevalue
240 | if (result.length !== 3) {
241 | grammarErr = 1;
242 | break;
243 | }
244 |
245 | /**************** START : variable ****************/
246 | //console.log('variable, operator, variablevalue',result);
247 | variable = result[0].match(/\(+(.*)/);
248 |
249 | //console.log('variable',variable);
250 |
251 | if (!variable || variable.length !== 2) {
252 | grammarErr = 1;
253 | break;
254 | }
255 |
256 | // get the variableName and push
257 | whereParams.push(variable[1]);
258 |
259 | // then replace the variable name with ??
260 | temp = replaceWhereParamsToQMarks(true, result[0], " ignore ", condType);
261 | if (!temp) {
262 | grammarErr = 1;
263 | break;
264 | }
265 | whereQuery += temp;
266 |
267 | /**************** END : variable ****************/
268 |
269 | /**************** START : operator and value ****************/
270 | comparisonOperator = getComparisonOperator(result[1]);
271 | if (!comparisonOperator) {
272 | grammarErr = 1;
273 | break;
274 | }
275 | whereQuery += comparisonOperator;
276 |
277 | // get the variableValue and push to params
278 | variableValue = result[2].match(/(.*?)\)/);
279 | if (!variableValue || variableValue.length !== 2) {
280 | grammarErr = 1;
281 | break;
282 | }
283 |
284 | if (comparisonOperator === " in ") {
285 | let obj = prepareInClauseParams(variableValue[1]);
286 | whereQuery += obj.whereQuery;
287 | whereParams = whereParams.concat(obj.whereParams);
288 | } else if (
289 | comparisonOperator === " like " ||
290 | comparisonOperator === " not like "
291 | ) {
292 | whereParams.push(prepareLikeValue(variableValue[1]));
293 | } else if (comparisonOperator === " is ") {
294 | whereParams.push(prepareIsValue(variableValue[1]));
295 | } else if (comparisonOperator === " between ") {
296 | let obj = prepareBetweenValue(variableValue[1]);
297 | whereQuery += obj.whereQuery;
298 | whereParams = whereParams.concat(obj.whereParams);
299 | //console.log(whereQuery, whereParams);
300 | } else {
301 | whereParams.push(variableValue[1]);
302 | }
303 |
304 | // then replace the variableValue with ?
305 | temp = replaceWhereParamsToQMarks(
306 | false,
307 | result[2],
308 | comparisonOperator,
309 | condType
310 | );
311 | if (!temp) {
312 | grammarErr = 1;
313 | break;
314 | }
315 | whereQuery += temp;
316 |
317 | if (numOfConditions.length !== -1 && i !== numOfConditions.length - 1) {
318 | //console.log('finding logical operator for',logicalOperatorsInClause[i]);
319 | logicalOperator = getLogicalOperator(logicalOperatorsInClause[i]);
320 |
321 | if (!logicalOperator) {
322 | grammarErr = 1;
323 | break;
324 | }
325 |
326 | whereQuery += getLogicalOperator(logicalOperatorsInClause[i]);
327 | }
328 | /**************** END : operator ****************/
329 | }
330 | }
331 |
332 | let obj = {};
333 |
334 | obj["query"] = "";
335 | obj["params"] = [];
336 | obj["err"] = grammarErr;
337 |
338 | // console.log(whereInQueryParams);
339 | // console.log(whereQuery);
340 | // console.log(whereParams);
341 | // console.log(grammarErr);
342 | // console.log('= = = = = = = = =');
343 |
344 | if (!grammarErr) {
345 | obj["query"] = whereQuery;
346 | obj["params"] = whereParams;
347 | }
348 |
349 | return obj;
350 | };
351 |
--------------------------------------------------------------------------------
/lib/xapi.js:
--------------------------------------------------------------------------------
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;
510 |
--------------------------------------------------------------------------------
/lib/xctrl.js:
--------------------------------------------------------------------------------
1 | //define class
2 | class xctrl {
3 | constructor(app, mysql) {
4 | this.app = app;
5 | this.mysql = mysql;
6 | }
7 |
8 | async create(req, res) {
9 | let query = "INSERT INTO ?? SET ?";
10 | let params = [];
11 |
12 | params.push(req.app.locals._tableName);
13 | params.push(req.body);
14 |
15 | var results = await this.mysql.exec(query, params);
16 | res.status(200).json(results);
17 | }
18 |
19 | async list(req, res) {
20 | let queryParamsObj = {};
21 | queryParamsObj.query = "";
22 | queryParamsObj.params = [];
23 |
24 | this.mysql.prepareListQuery(req, res, queryParamsObj, 0);
25 |
26 | let results = await this.mysql.exec(
27 | queryParamsObj.query,
28 | queryParamsObj.params
29 | );
30 | res.status(200).json(results);
31 | }
32 |
33 | async nestedList(req, res) {
34 | let queryParamsObj = {};
35 | queryParamsObj.query = "";
36 | queryParamsObj.params = [];
37 |
38 | this.mysql.prepareListQuery(req, res, queryParamsObj, 1);
39 |
40 | let results = await this.mysql.exec(
41 | queryParamsObj.query,
42 | queryParamsObj.params
43 | );
44 | res.status(200).json(results);
45 | }
46 |
47 | async findOne(req, res) {
48 | let queryParamsObj = {};
49 | queryParamsObj.query = "";
50 | queryParamsObj.params = [];
51 |
52 | this.mysql.prepareListQuery(req, res, queryParamsObj, 2);
53 |
54 | let results = await this.mysql.exec(
55 | queryParamsObj.query,
56 | queryParamsObj.params
57 | );
58 | res.status(200).json(results);
59 | }
60 |
61 | async read(req, res) {
62 | let query = "select * from ?? where ";
63 | let params = [];
64 |
65 | params.push(req.app.locals._tableName);
66 |
67 | let clause = this.mysql.getPrimaryKeyWhereClause(
68 | req.app.locals._tableName,
69 | req.params.id.split("___")
70 | );
71 |
72 | if (!clause) {
73 | return res.status(400).send({
74 | error:
75 | "Table is made of composite primary keys - all keys were not in input"
76 | });
77 | }
78 |
79 | query += clause;
80 | query += " LIMIT 1";
81 |
82 | let results = await this.mysql.exec(query, params);
83 | res.status(200).json(results);
84 | }
85 |
86 | async exists(req, res) {
87 | let query = "select * from ?? where ";
88 | let params = [];
89 |
90 | params.push(req.app.locals._tableName);
91 |
92 | let clause = this.mysql.getPrimaryKeyWhereClause(
93 | req.app.locals._tableName,
94 | req.params.id.split("___")
95 | );
96 |
97 | if (!clause) {
98 | return res.status(400).send({
99 | error:
100 | "Table is made of composite primary keys - all keys were not in input"
101 | });
102 | }
103 |
104 | query += clause;
105 | query += " LIMIT 1";
106 |
107 | let results = await this.mysql.exec(query, params);
108 | res.status(200).json(results);
109 | }
110 |
111 | async update(req, res) {
112 | let query = "REPLACE INTO ?? SET ?";
113 | let params = [];
114 |
115 | params.push(req.app.locals._tableName);
116 | params.push(req.body);
117 |
118 | var results = await this.mysql.exec(query, params);
119 | res.status(200).json(results);
120 | }
121 |
122 | async patch(req, res) {
123 | let query = "UPDATE ?? SET ";
124 | let keys = Object.keys(req.body);
125 |
126 | // SET clause
127 | let updateKeys = "";
128 | for (let i = 0; i < keys.length; ++i) {
129 | updateKeys += keys[i] + " = ? ";
130 | if (i !== keys.length - 1) updateKeys += ", ";
131 | }
132 |
133 | // where clause
134 | query += updateKeys + " where ";
135 | let clause = this.mysql.getPrimaryKeyWhereClause(
136 | req.app.locals._tableName,
137 | req.params.id.split("___")
138 | );
139 |
140 | if (!clause) {
141 | return res.status(400).send({
142 | error:
143 | "Table is made of composite primary keys - all keys were not in input"
144 | });
145 | }
146 |
147 | query += clause;
148 |
149 | // params
150 | let params = [];
151 | params.push(req.app.locals._tableName);
152 | params = params.concat(Object.values(req.body));
153 |
154 | let results = await this.mysql.exec(query, params);
155 | res.status(200).json(results);
156 | }
157 |
158 | async delete(req, res) {
159 | let query = "DELETE FROM ?? WHERE ";
160 | let params = [];
161 |
162 | params.push(req.app.locals._tableName);
163 |
164 | let clause = this.mysql.getPrimaryKeyWhereClause(
165 | req.app.locals._tableName,
166 | req.params.id.split("___")
167 | );
168 |
169 | if (!clause) {
170 | return res.status(400).send({
171 | error:
172 | "Table is made of composite primary keys - all keys were not in input"
173 | });
174 | }
175 |
176 | query += clause;
177 |
178 | let results = await this.mysql.exec(query, params);
179 | res.status(200).json(results);
180 | }
181 |
182 | async bulkInsert(req, res) {
183 | let queryParamsObj = {};
184 | queryParamsObj.query = "";
185 | queryParamsObj.params = [];
186 | let results = [];
187 |
188 | //console.log(req.app.locals._tableName, req.body);
189 |
190 | this.mysql.prepareBulkInsert(
191 | req.app.locals._tableName,
192 | req.body,
193 | queryParamsObj
194 | );
195 |
196 | results = await this.mysql.exec(
197 | queryParamsObj.query,
198 | queryParamsObj.params
199 | );
200 | res.status(200).json(results);
201 | }
202 |
203 | async bulkDelete(req, res) {
204 | let query = "delete from ?? where ?? in ";
205 | let params = [];
206 |
207 | params.push(req.app.locals._tableName);
208 | params.push(this.mysql.getPrimaryKeyName(req.app.locals._tableName));
209 |
210 | query += "(";
211 |
212 | if (req.query && req.query._ids) {
213 | let ids = req.query._ids.split(",");
214 | for (var i = 0; i < ids.length; ++i) {
215 | if (i) {
216 | query += ",";
217 | }
218 | query += "?";
219 | params.push(ids[i]);
220 | }
221 | }
222 |
223 | query += ")";
224 |
225 | //console.log(query, params);
226 |
227 | var results = await this.mysql.exec(query, params);
228 | res.status(200).json(results);
229 | }
230 |
231 | async bulkRead(req, res) {
232 | let queryParamsObj = {};
233 | queryParamsObj.query = "";
234 | queryParamsObj.params = [];
235 |
236 | this.mysql.prepareListQuery(req, res, queryParamsObj, 3);
237 |
238 | //console.log(queryParamsObj.query, queryParamsObj.params);
239 |
240 | let results = await this.mysql.exec(
241 | queryParamsObj.query,
242 | queryParamsObj.params
243 | );
244 | res.status(200).json(results);
245 | }
246 |
247 | async count(req, res) {
248 | let queryParams = {};
249 |
250 | queryParams.query = "select count(1) as no_of_rows from ?? ";
251 | queryParams.params = [];
252 |
253 | queryParams.params.push(req.app.locals._tableName);
254 |
255 | this.mysql.getWhereClause(
256 | req.query._where,
257 | req.app.locals._tableName,
258 | queryParams,
259 | " where "
260 | );
261 |
262 | let results = await this.mysql.exec(queryParams.query, queryParams.params);
263 | res.status(200).json(results);
264 | }
265 |
266 | async distinct(req, res) {
267 | let queryParamsObj = {};
268 | queryParamsObj.query = "";
269 | queryParamsObj.params = [];
270 |
271 | this.mysql.prepareListQuery(req, res, queryParamsObj, 4);
272 |
273 | let results = await this.mysql.exec(
274 | queryParamsObj.query,
275 | queryParamsObj.params
276 | );
277 | res.status(200).json(results);
278 | }
279 |
280 | async groupBy(req, res) {
281 | if (req.query && req.query._fields) {
282 | let queryParamsObj = {};
283 | queryParamsObj.query = "select ";
284 | queryParamsObj.params = [];
285 |
286 | /**************** add columns and group by columns ****************/
287 | this.mysql.getColumnsForSelectStmt(
288 | req.app.locals._tableName,
289 | req.query,
290 | queryParamsObj
291 | );
292 |
293 | queryParamsObj.query += ",count(*) as _count from ?? group by ";
294 | let tableName = req.app.locals._tableName;
295 | queryParamsObj.params.push(tableName);
296 |
297 | this.mysql.getColumnsForGroupBy(
298 | req.app.locals._tableName,
299 | req.query,
300 | queryParamsObj
301 | );
302 |
303 | if (!req.query._sort) {
304 | req.query._sort = {};
305 | req.query._sort = "-_count";
306 | }
307 |
308 | /**************** add having clause ****************/
309 | this.mysql.getHavingClause(
310 | req.query._having,
311 | req.app.locals._tableName,
312 | queryParamsObj,
313 | " having "
314 | );
315 |
316 | /**************** add orderby clause ****************/
317 | this.mysql.getOrderByClause(req.query, tableName, queryParamsObj);
318 |
319 | //console.log(queryParamsObj.query, queryParamsObj.params);
320 | var results = await this.mysql.exec(
321 | queryParamsObj.query,
322 | queryParamsObj.params
323 | );
324 |
325 | res.status(200).json(results);
326 | } else {
327 | res
328 | .status(400)
329 | .json({
330 | message:
331 | "Missing _fields query params eg: /api/tableName/groupby?_fields=column1"
332 | });
333 | }
334 | }
335 |
336 | async ugroupby(req, res) {
337 | if (req.query && req.query._fields) {
338 | let queryParamsObj = {};
339 | queryParamsObj.query = "";
340 | queryParamsObj.params = [];
341 | let uGrpByResults = {};
342 |
343 | /**************** add fields with count(*) *****************/
344 | let fields = req.query._fields.split(",");
345 |
346 | for (var i = 0; i < fields.length; ++i) {
347 | uGrpByResults[fields[i]] = [];
348 |
349 | if (i) {
350 | queryParamsObj.query += " UNION ";
351 | }
352 | queryParamsObj.query +=
353 | " SELECT IFNULL(CONCAT(?,?,??),?) as ugroupby, count(*) as _count from ?? GROUP BY ?? ";
354 | queryParamsObj.params.push(fields[i]);
355 | queryParamsObj.params.push("~");
356 | queryParamsObj.params.push(fields[i]);
357 | queryParamsObj.params.push(fields[i] + "~");
358 | queryParamsObj.params.push(req.app.locals._tableName);
359 | queryParamsObj.params.push(fields[i]);
360 | }
361 |
362 | //console.log(queryParamsObj.query, queryParamsObj.params);
363 | var results = await this.mysql.exec(
364 | queryParamsObj.query,
365 | queryParamsObj.params
366 | );
367 |
368 | for (var i = 0; i < results.length; ++i) {
369 | let grpByColName = results[i]["ugroupby"].split("~")[0];
370 | let grpByColValue = results[i]["ugroupby"].split("~")[1];
371 |
372 | let obj = {};
373 | obj[grpByColValue] = results[i]["_count"];
374 |
375 | uGrpByResults[grpByColName].push(obj);
376 | }
377 |
378 | res.status(200).json(uGrpByResults);
379 | } else {
380 | res
381 | .status(400)
382 | .json({
383 | message:
384 | "Missing _fields query params eg: /api/tableName/ugroupby?_fields=column1,column2"
385 | });
386 | }
387 | }
388 |
389 | async aggregate(req, res) {
390 | if (req.query && req.query._fields) {
391 | let tableName = req.app.locals._tableName;
392 | let query = "select ";
393 | let params = [];
394 | let fields = req.query._fields.split(",");
395 |
396 | for (var i = 0; i < fields.length; ++i) {
397 | if (i) {
398 | query = query + ",";
399 | }
400 | query =
401 | query +
402 | " min(??) as ?,max(??) as ?,avg(??) as ?,sum(??) as ?,stddev(??) as ?,variance(??) as ? ";
403 | params.push(fields[i]);
404 | params.push("min_of_" + fields[i]);
405 | params.push(fields[i]);
406 | params.push("max_of_" + fields[i]);
407 | params.push(fields[i]);
408 | params.push("avg_of_" + fields[i]);
409 | params.push(fields[i]);
410 | params.push("sum_of_" + fields[i]);
411 | params.push(fields[i]);
412 | params.push("stddev_of_" + fields[i]);
413 | params.push(fields[i]);
414 | params.push("variance_of_" + fields[i]);
415 | }
416 |
417 | query = query + " from ??";
418 | params.push(tableName);
419 |
420 | var results = await this.mysql.exec(query, params);
421 |
422 | res.status(200).json(results);
423 | } else {
424 | res
425 | .status(400)
426 | .json({
427 | message:
428 | "Missing _fields in query params eg: /api/tableName/aggregate?_fields=numericColumn1"
429 | });
430 | }
431 | }
432 |
433 | async chart(req, res) {
434 | let query = "";
435 | let params = [];
436 | let obj = {};
437 |
438 | if (req.query) {
439 | let isRange = false;
440 | if (req.query.range) {
441 | isRange = true;
442 | }
443 |
444 | if (req.query && req.query.min && req.query.max && req.query.step) {
445 | //console.log(req.params.min, req.params.max, req.params.step);
446 |
447 | obj = this.mysql.getChartQueryAndParamsFromMinMaxStep(
448 | req.app.locals._tableName,
449 | req.query._fields,
450 | parseInt(req.query.min),
451 | parseInt(req.query.max),
452 | parseInt(req.query.step),
453 | isRange
454 | );
455 | } else if (
456 | req.query &&
457 | req.query.steparray &&
458 | req.query.steparray.length > 1
459 | ) {
460 | obj = this.mysql.getChartQueryAndParamsFromStepArray(
461 | req.app.locals._tableName,
462 | req.query._fields,
463 | req.query.steparray.split(",").map(Number),
464 | isRange
465 | );
466 | } else if (
467 | req.query &&
468 | req.query.steppair &&
469 | req.query.steppair.length > 1
470 | ) {
471 | obj = this.mysql.getChartQueryAndParamsFromStepPair(
472 | req.app.locals._tableName,
473 | req.query._fields,
474 | req.query.steppair.split(",").map(Number),
475 | false
476 | );
477 | } else {
478 | query =
479 | "select min(??) as min,max(??) as max,stddev(??) as stddev,avg(??) as avg from ??";
480 | params = [];
481 |
482 | params.push(req.query._fields);
483 | params.push(req.query._fields);
484 | params.push(req.query._fields);
485 | params.push(req.query._fields);
486 | params.push(req.app.locals._tableName);
487 |
488 | let _this = this;
489 |
490 | let results = await _this.mysql.exec(query, params);
491 |
492 | //console.log(results, results['max'], req.params);
493 |
494 | obj = _this.mysql.getChartQueryAndParamsFromMinMaxStddev(
495 | req.app.locals._tableName,
496 | req.query._fields,
497 | results[0]["min"],
498 | results[0]["max"],
499 | results[0]["stddev"],
500 | isRange
501 | );
502 | }
503 |
504 | this.mysql.getWhereClause(
505 | req.query._where,
506 | req.app.locals._tableName,
507 | obj,
508 | " where "
509 | );
510 |
511 | let results = await this.mysql.exec(obj.query, obj.params);
512 |
513 | res.status(200).json(results);
514 | } else {
515 | res
516 | .status(400)
517 | .json({
518 | message:
519 | "Missing _fields in query params eg: /api/tableName/chart?_fields=numericColumn1"
520 | });
521 | }
522 | }
523 |
524 | async autoChart(req, res) {
525 | let query = "describe ??";
526 | let params = [req.app.locals._tableName];
527 | let obj = {};
528 | let results = [];
529 |
530 | let isRange = false;
531 | if (req.query.range) {
532 | isRange = true;
533 | }
534 |
535 | let describeResults = await this.mysql.exec(query, params);
536 |
537 | //console.log(describeResults);
538 |
539 | for (var i = 0; i < describeResults.length; ++i) {
540 | //console.log('is this numeric column', describeResults[i]['Type']);
541 |
542 | if (
543 | describeResults[i]["Key"] !== "PRI" &&
544 | this.mysql.isTypeOfColumnNumber(describeResults[i]["Type"])
545 | ) {
546 | query =
547 | "select min(??) as min,max(??) as max,stddev(??) as stddev,avg(??) as avg from ??";
548 | params = [];
549 |
550 | params.push(describeResults[i]["Field"]);
551 | params.push(describeResults[i]["Field"]);
552 | params.push(describeResults[i]["Field"]);
553 | params.push(describeResults[i]["Field"]);
554 | params.push(req.app.locals._tableName);
555 |
556 | let _this = this;
557 |
558 | let minMaxResults = await _this.mysql.exec(query, params);
559 |
560 | //console.log(minMaxResults, minMaxResults['max'], req.params);
561 |
562 | query = "";
563 | params = [];
564 |
565 | obj = _this.mysql.getChartQueryAndParamsFromMinMaxStddev(
566 | req.app.locals._tableName,
567 | describeResults[i]["Field"],
568 | minMaxResults[0]["min"],
569 | minMaxResults[0]["max"],
570 | minMaxResults[0]["stddev"],
571 | isRange
572 | );
573 |
574 | let r = await this.mysql.exec(obj.query, obj.params);
575 |
576 | let resultObj = {};
577 | resultObj["column"] = describeResults[i]["Field"];
578 | resultObj["chart"] = r;
579 |
580 | results.push(resultObj);
581 | }
582 | }
583 |
584 | res.status(200).json(results);
585 | }
586 | }
587 |
588 | //expose class
589 | module.exports = xctrl;
590 |
--------------------------------------------------------------------------------
/lib/xsql.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const mysql = require("mysql");
4 | const dataHelp = require("./util/data.helper.js");
5 | const whereHelp = require("./util/whereClause.helper.js");
6 | const assert = require("assert");
7 |
8 | //define class§
9 | class Xsql {
10 | constructor(sqlConfig, pool) {
11 | //define this variables
12 | this.sqlConfig = {};
13 | this.pool = {};
14 | this.metaDb = {};
15 | this.metaDb.tables = {};
16 | this.metaDb.routines = [];
17 |
18 | this.sqlConfig = sqlConfig;
19 | this.pool = pool;
20 | }
21 |
22 | /**************** START : Cache functions ****************/
23 | init(cbk) {
24 | this.dbCacheInitAsync((err, results) => {
25 | cbk(err, results);
26 | });
27 | }
28 |
29 | dbCacheInitAsync(cbk) {
30 | let self = this;
31 |
32 | self.pool.query(
33 | dataHelp.getSchemaQuery(),
34 | [this.sqlConfig.database],
35 | (err, results) => {
36 | if (err) {
37 | console.log("Cache init failed during database reading");
38 | console.log(err, results);
39 | cbk(err, results);
40 | } else {
41 | for (var i = 0; i < results.length; ++i) {
42 | let keys = Object.keys(results[i]);
43 |
44 | for (var j = 0; j < keys.length; ++j) {
45 | let value = results[i][keys[j]];
46 |
47 | results[i][keys[j].toLowerCase()] = value;
48 |
49 | //console.log(value);
50 | }
51 | }
52 |
53 | self.iterateToCacheTables(results);
54 | self.iterateToCacheTablePks(results);
55 | self.iterateToCacheTableColumns(results);
56 | self.iterateToCacheTableFks(results);
57 |
58 | // osx mysql server has limitations related to open_tables
59 | self.pool.query("FLUSH TABLES", [], (err, results) => {
60 | self.pool.query(dataHelp.getRoutines(), [this.sqlConfig.database], (err, results) => {
61 | if (err) {
62 | cbk(err, results)
63 | } else {
64 | self.iterateToCacheRoutines(results)
65 | cbk(null, null)
66 | }
67 | })
68 | });
69 | }
70 | }
71 | );
72 | }
73 |
74 | iterateToCacheTables(schemaResults) {
75 | for (let i = 0; i < schemaResults.length; ++i) {
76 | let schemaRow = schemaResults[i];
77 |
78 | let tableName = schemaRow["table_name"];
79 |
80 | if (!(tableName in this.metaDb.tables)) {
81 | this.metaDb.tables[tableName] = {};
82 | this.metaDb.tables[tableName]["primaryKeys"] = [];
83 | this.metaDb.tables[tableName]["foreignKeys"] = [];
84 | this.metaDb.tables[tableName]["columns"] = [];
85 | this.metaDb.tables[tableName]["indicies"] = [];
86 | this.metaDb.tables[tableName]["isView"] = schemaRow["isView"];
87 | }
88 | }
89 | }
90 |
91 | iterateToCacheRoutines(routineResults) {
92 | for (let i = 0; i < routineResults.length; i++) {
93 | const routine = routineResults[i]
94 | const routineName = routine.routine_name
95 | this.metaDb.routines.push(routineName)
96 | }
97 | }
98 |
99 | iterateToCacheTableColumns(schemaResults) {
100 | for (let i = 0; i < schemaResults.length; ++i) {
101 | let schemaRow = schemaResults[i];
102 | let tableName = schemaRow["table_name"];
103 | let col = {};
104 | col["column_name"] = schemaRow["column_name"];
105 | col["ordinal_position"] = schemaRow["ordinal_position"];
106 | col["column_key"] = schemaRow["column_key"];
107 | col["data_type"] = schemaRow["data_type"];
108 | col["column_type"] = schemaRow["column_type"];
109 |
110 | dataHelp.findOrInsertObjectArrayByKey(
111 | col,
112 | "column_name",
113 | this.metaDb.tables[tableName]["columns"]
114 | );
115 | }
116 | }
117 |
118 | iterateToCacheTablePks(schemaResults) {
119 | for (let i = 0; i < schemaResults.length; ++i) {
120 | let schemaRow = schemaResults[i];
121 | let tableName = schemaRow["table_name"];
122 |
123 | if (schemaRow["column_key"] === "PRI") {
124 | let pk = {};
125 | pk["column_name"] = schemaRow["column_name"];
126 | pk["ordinal_position"] = schemaRow["ordinal_position"];
127 | pk["column_key"] = schemaRow["column_key"];
128 | pk["data_type"] = schemaRow["data_type"];
129 | pk["column_type"] = schemaRow["column_type"];
130 |
131 | dataHelp.findOrInsertObjectArrayByKey(
132 | pk,
133 | "column_name",
134 | this.metaDb.tables[tableName]["primaryKeys"]
135 | );
136 | }
137 | }
138 | }
139 |
140 | iterateToCacheTableFks(schemaResults) {
141 | for (let i = 0; i < schemaResults.length; ++i) {
142 | let schemaRow = schemaResults[i];
143 | let tableName = schemaRow["table_name"];
144 |
145 | if (schemaRow["referenced_table_name"]) {
146 | let fk = {};
147 |
148 | fk["column_name"] = schemaRow["column_name"];
149 | fk["table_name"] = schemaRow["table_name"];
150 | fk["referenced_table_name"] = schemaRow["referenced_table_name"];
151 | fk["referenced_column_name"] = schemaRow["referenced_column_name"];
152 | fk["data_type"] = schemaRow["data_type"];
153 | fk["column_type"] = schemaRow["column_type"];
154 |
155 | dataHelp.findOrInsertObjectArrayByKey(
156 | fk,
157 | "column_name",
158 | this.metaDb.tables[tableName]["foreignKeys"]
159 | );
160 |
161 | //console.log(fk['referenced_table_name'],fk['referenced_column_name'],tableName, schemaRow['column_name'], this.metaDb.tables[tableName]['foreignKeys'].length)
162 | }
163 | }
164 | }
165 |
166 | /**************** END : Cache functions ****************/
167 |
168 | exec(query, params) {
169 | let _this = this;
170 | return new Promise(function(resolve, reject) {
171 | //console.log('mysql>', query, params);
172 | _this.pool.query(query, params, function(error, rows, _fields) {
173 | if (error) {
174 | console.log("mysql> ", error);
175 | return reject(error);
176 | }
177 | return resolve(rows);
178 | });
179 | });
180 | }
181 |
182 | typeOfColumn(Type) {
183 | //TODO: Im sure there are more types to handle here
184 | const strTypes = [
185 | "varchar",
186 | "text",
187 | "char",
188 | "tinytext",
189 | "mediumtext",
190 | "longtext",
191 | "blob",
192 | "mediumblob",
193 | "longblob"
194 | ];
195 | const intTypes = [
196 | "int",
197 | "long",
198 | "smallint",
199 | "mediumint",
200 | "bigint",
201 | "tinyint"
202 | ];
203 | const flatTypes = ["float", "double", "decimal"];
204 | const dateTypes = ["date", "datetime", "timestamp", "time", "year"];
205 |
206 | if (dataHelp.getType(Type, strTypes)) {
207 | return "string";
208 | } else if (dataHelp.getType(Type, intTypes)) {
209 | return "int";
210 | } else if (dataHelp.getType(Type, flatTypes)) {
211 | return "float";
212 | } else if (dataHelp.getType(Type, dateTypes)) {
213 | return "date";
214 | } else {
215 | return "unknown";
216 | }
217 | }
218 |
219 | isTypeOfColumnNumber(Type) {
220 | //console.log(Type, this.typeOfColumn(Type));
221 | return (
222 | "int" === this.typeOfColumn(Type) || "float" === this.typeOfColumn(Type)
223 | );
224 | }
225 |
226 | getLimitClause(reqParams) {
227 | //defaults
228 | reqParams._index = 0;
229 | reqParams._len = 20;
230 |
231 | if ("_size" in reqParams) {
232 | if (parseInt(reqParams._size) > 0 && parseInt(reqParams._size) <= 100) {
233 | reqParams._len = parseInt(reqParams._size);
234 | } else if (parseInt(reqParams._size) > 100) {
235 | reqParams._len = 100;
236 | }
237 | }
238 |
239 | if ("_p" in reqParams && parseInt(reqParams._p) > 0) {
240 | reqParams._index = parseInt(reqParams._p) * reqParams._len;
241 | }
242 |
243 | //console.log(reqParams._index, reqParams._len);
244 |
245 | return [reqParams._index, reqParams._len];
246 | }
247 |
248 | prepareBulkInsert(tableName, objectArray, queryParamsObj) {
249 | if (tableName in this.metaDb.tables && objectArray) {
250 | let insObj = objectArray[0];
251 |
252 | // goal => insert into ?? (?,?..?) values ? [tablName, col1,col2...coln,[[ObjValues_1],[ObjValues_2],...[ObjValues_N]]
253 | queryParamsObj.query = " INSERT INTO ?? ( ";
254 | queryParamsObj.params.push(tableName);
255 |
256 | let cols = [];
257 | let colPresent = false;
258 |
259 | /**************** START : prepare column names to be inserted ****************/
260 | // iterate over all column in table and have only ones existing in objects to be inserted
261 | for (
262 | var i = 0;
263 | i < this.metaDb.tables[tableName]["columns"].length;
264 | ++i
265 | ) {
266 | let colName = this.metaDb.tables[tableName]["columns"][i][
267 | "column_name"
268 | ];
269 |
270 | if (colName in insObj) {
271 | if (colPresent) {
272 | queryParamsObj.query += ",";
273 | }
274 |
275 | queryParamsObj.query += colName;
276 |
277 | colPresent = true;
278 | }
279 |
280 | cols.push(colName);
281 |
282 | //console.log('> > ', queryParamsObj.query);
283 | }
284 |
285 | queryParamsObj.query += " ) values ?";
286 | /**************** END : prepare column names to be inserted ****************/
287 |
288 | /**************** START : prepare value object in prepared statement ****************/
289 | // iterate over sent object array
290 | let arrOfArr = [];
291 | for (var i = 0; i < objectArray.length; ++i) {
292 | let arrValues = [];
293 | for (var j = 0; j < cols.length; ++j) {
294 | if (cols[j] in objectArray[i])
295 | arrValues.push(objectArray[i][cols[j]]);
296 | }
297 | arrOfArr.push(arrValues);
298 | }
299 | queryParamsObj.params.push(arrOfArr);
300 | /**************** END : prepare value object in prepared statement ****************/
301 | }
302 | }
303 |
304 | getGroupByClause(_groupby, tableName, queryParamsObj) {
305 | if (_groupby) {
306 | queryParamsObj.query += " group by " + _groupby + " ";
307 | return _groupby;
308 | }
309 | }
310 |
311 | getHavingClause(_having, tableName, queryParamsObj) {
312 | if (_having) {
313 | let whereClauseObj = whereHelp.getConditionClause(_having, "having");
314 |
315 | if (whereClauseObj.err === 0) {
316 | queryParamsObj.query =
317 | queryParamsObj.query + " having " + whereClauseObj.query;
318 | queryParamsObj.params = queryParamsObj.params.concat(
319 | whereClauseObj.params
320 | );
321 | }
322 |
323 | //console.log('> > > after where clause filling up:', queryParamsObj.query, queryParamsObj.params);
324 | }
325 | }
326 |
327 | getWhereClause(queryparams, tableName, queryParamsObj, appendToWhere) {
328 | if (queryparams) {
329 | let whereClauseObj = whereHelp.getConditionClause(queryparams);
330 |
331 | if (whereClauseObj.err === 0) {
332 | queryParamsObj.query =
333 | queryParamsObj.query + appendToWhere + whereClauseObj.query;
334 | queryParamsObj.params = queryParamsObj.params.concat(
335 | whereClauseObj.params
336 | );
337 | }
338 | }
339 | }
340 |
341 | getOrderByClause(queryparams, tableName, queryParamsObj) {
342 | if (queryparams._sort) {
343 | queryParamsObj.query += " ORDER BY ";
344 |
345 | let orderByCols = queryparams._sort.split(",");
346 |
347 | for (let i = 0; i < orderByCols.length; ++i) {
348 | if (i) {
349 | queryParamsObj.query += ", ";
350 | }
351 | const aggregationFunction = this.getAggregationFunction(orderByCols[i]);
352 | const columnName = this.getColumnNameWithoutAggregationFunctions(orderByCols[i]);
353 | const orderByDirection = orderByCols[i][0] === "-" ? 'DESC' : 'ASC';
354 |
355 | if (aggregationFunction) {
356 | queryParamsObj.query += `${aggregationFunction}(??) ${orderByDirection}`;
357 | queryParamsObj.params.push(columnName);
358 | } else {
359 | queryParamsObj.query += `?? ${orderByDirection}`;
360 | queryParamsObj.params.push(columnName);
361 | }
362 | }
363 | }
364 | }
365 |
366 | getColumnsForSelectStmtWithGrpBy(reqQueryParams, tableName, queryParamsObj) {
367 | let grpByCols = reqQueryParams._groupby.split(",");
368 |
369 | for (var i = 0; i < grpByCols.length; ++i) {
370 | if (i) {
371 | queryParamsObj.query += ",";
372 | }
373 | queryParamsObj.query += " ??";
374 | queryParamsObj.params.push(grpByCols[i]);
375 | }
376 |
377 | queryParamsObj.query += ",count(1) as _count ";
378 | }
379 |
380 | getColumnsForGroupBy(tableName, reqQueryParams, queryParamsObj) {
381 | const updatedQueryParams = Object.assign({}, reqQueryParams);
382 | if ('_groupbyfields' in updatedQueryParams) {
383 | // allows you to group by different fields than you have in the select
384 | updatedQueryParams['_fields'] = updatedQueryParams['_groupbyfields'];
385 | }
386 |
387 | return this.getColumnsForSelectStmt(tableName, updatedQueryParams, queryParamsObj)
388 | }
389 |
390 | getColumnsForSelectStmt(tableName, reqQueryParams, queryParamsObj) {
391 | let table = this.metaDb.tables[tableName];
392 | let cols = [];
393 | let _fieldsInQuery = [];
394 | let removeFieldsObj = {};
395 |
396 | // populate _fields array from query params
397 | if ("_fields" in reqQueryParams) {
398 | _fieldsInQuery = reqQueryParams["_fields"].split(",");
399 | } else {
400 | queryParamsObj.query += " * ";
401 | return " * ";
402 | }
403 |
404 | // get column name in _fields and mark column name which start with '-'
405 | for (let i = 0; i < _fieldsInQuery.length; ++i) {
406 | if (_fieldsInQuery[i][0] === "-") {
407 | removeFieldsObj[
408 | _fieldsInQuery[i].substring(1, _fieldsInQuery[i].length)
409 | ] = 1;
410 | } else {
411 | cols.push(_fieldsInQuery[i]);
412 | }
413 | }
414 |
415 | if (!cols.length) {
416 | // for each column in table - add only which are not in removeFieldsObj
417 | for (let i = 0; i < table["columns"].length; ++i) {
418 | if (!(table["columns"][i]["column_name"] in removeFieldsObj)) {
419 | cols.push(table["columns"][i]["column_name"]);
420 | }
421 | }
422 | } else {
423 | cols = this.removeUnknownColumns(cols, tableName);
424 | }
425 |
426 | for (var i = 0; i < cols.length; ++i) {
427 | if (i) {
428 | queryParamsObj.query += ",";
429 | }
430 | const aggregationFunction = this.getAggregationFunction(cols[i]);
431 |
432 | if (aggregationFunction) {
433 | queryParamsObj.query += `${aggregationFunction}(??)`;
434 | const columnName = this.getColumnNameWithoutAggregationFunctions(cols[i]);
435 | queryParamsObj.params.push(columnName);
436 | } else {
437 | queryParamsObj.query += "??";
438 | queryParamsObj.params.push(cols[i]);
439 | }
440 | }
441 |
442 | return cols.join(",");
443 | }
444 |
445 | getAggregationFunction(rawColumnName) {
446 | const AGGREGATION_FUNCTION_REGEX = /^[-]?(AVG|BIT_AND|BIT_OR|BIT_XOR|COUNT|COUNTDISTINCT|GROUP_CONCAT|JSON_ARRAYAGG|JSON_OBJECTAGG|MAX|MIN|STD|STDDEV|STDDEV_POP|STDDEV_SAMP|SUM|VAR_POP|VAR_SAMP|VARIANCE)\((.*)\)$/i;
447 | const aggFuncMatch = rawColumnName.match(AGGREGATION_FUNCTION_REGEX);
448 | if (aggFuncMatch && aggFuncMatch.length === 3) {
449 | // match will look like (3) ["AVG(timestamp)", "AVG", "timestamp", index: 0, input: "AVG(timestamp)", groups: undefined]
450 | return aggFuncMatch[1];
451 | }
452 | return null;
453 | }
454 |
455 | getColumnNameWithoutAggregationFunctions(rawColumnName) {
456 | const AGGREGATION_FUNCTION_REGEX = /^[-]?(AVG|BIT_AND|BIT_OR|BIT_XOR|COUNT|COUNTDISTINCT|GROUP_CONCAT|JSON_ARRAYAGG|JSON_OBJECTAGG|MAX|MIN|STD|STDDEV|STDDEV_POP|STDDEV_SAMP|SUM|VAR_POP|VAR_SAMP|VARIANCE)\((.*)\)$/i;
457 | const aggFuncMatch = rawColumnName.match(AGGREGATION_FUNCTION_REGEX);
458 | if (aggFuncMatch && aggFuncMatch.length === 3) {
459 | // match will look like (3) ["AVG(timestamp)", "AVG", "timestamp", index: 0, input: "AVG(timestamp)", groups: undefined]
460 | return aggFuncMatch[2];
461 | }
462 | return rawColumnName.replace(/-/, '');
463 | }
464 |
465 | removeUnknownColumns(inputColumns, tableName) {
466 | let cols = inputColumns;
467 | let unknown_cols_in_input = [];
468 | let shadowCols = [];
469 | let tableColumns = this.metaDb.tables[tableName]["columns"];
470 |
471 | // find unknown fields if any
472 | for (var j = 0; j < cols.length; ++j) {
473 | let found = 0;
474 | // Used to allow aggregation functions like AVG(timestamp)
475 | let columnNameWithoutAggregationClauses = this.getColumnNameWithoutAggregationFunctions(cols[j]);
476 |
477 | for (var i = 0; i < tableColumns.length; ++i) {
478 | if (tableColumns[i]["column_name"] === columnNameWithoutAggregationClauses) {
479 | found = 1;
480 | break;
481 | }
482 | }
483 |
484 | if (!found) {
485 | unknown_cols_in_input.push(j);
486 | }
487 | }
488 |
489 | // if there are unknown fields - remove and ignore 'em
490 | if (unknown_cols_in_input.length) {
491 | for (var i = 0; i < cols.length; ++i) {
492 | if (unknown_cols_in_input.indexOf(i) === -1) {
493 | shadowCols.push(cols[i]);
494 | }
495 | }
496 |
497 | cols = [];
498 | cols = shadowCols;
499 | }
500 |
501 | return cols;
502 | }
503 |
504 | getPrimaryKeyName(tableName) {
505 | let pk = null;
506 | if (tableName in this.metaDb.tables) {
507 | pk = this.metaDb.tables[tableName].primaryKeys[0]["column_name"];
508 | }
509 | return pk;
510 | }
511 |
512 | getPrimaryKeyWhereClause(tableName, pksValues) {
513 | let whereClause = "";
514 | let whereCol = "";
515 | let whereValue = "";
516 | let pks = [];
517 |
518 | if (tableName in this.metaDb.tables) {
519 | pks = this.metaDb.tables[tableName].primaryKeys;
520 | } else {
521 | return null;
522 | }
523 |
524 | // number of primary keys in table and one sent should be same
525 | if (pksValues.length !== pks.length) {
526 | return null;
527 | }
528 |
529 | // get a where clause out of the above columnNames and their values
530 | for (let i = 0; i < pks.length; ++i) {
531 | let type = dataHelp.getColumnType(pks[i]);
532 |
533 | whereCol = pks[i]["column_name"];
534 |
535 | if (type === "string") {
536 | whereValue = mysql.escape(pksValues[i]);
537 | } else if (type === "int") {
538 | whereValue = parseInt(pksValues[i]);
539 | } else if (type === "float") {
540 | whereValue = parseFloat(pksValues[i]);
541 | } else if (type === "date") {
542 | whereValue = Date(pksValues[i]);
543 | } else {
544 | console.error(pks[i]);
545 | assert(false, "Unhandled type of primary key");
546 | }
547 |
548 | if (i) {
549 | whereClause += " and ";
550 | }
551 |
552 | whereClause += whereCol + " = " + whereValue;
553 | }
554 |
555 | return whereClause;
556 | }
557 |
558 | getForeignKeyWhereClause(parentTable, parentId, childTable) {
559 | let whereValue = "";
560 |
561 | //get all foreign keys of child table
562 | let fks = this.metaDb.tables[childTable].foreignKeys;
563 | let fk = dataHelp.findObjectInArrayByKey(
564 | "referenced_table_name",
565 | parentTable,
566 | fks
567 | );
568 | let whereCol = fk["column_name"];
569 | let colType = dataHelp.getColumnType(fk);
570 |
571 | if (colType === "string") {
572 | whereValue = mysql.escape(parentId);
573 | } else if (colType === "int") {
574 | whereValue = mysql.escape(parseInt(parentId));
575 | } else if (colType === "float") {
576 | whereValue = mysql.escape(parseFloat(parentId));
577 | } else if (colType === "date") {
578 | whereValue = mysql.escape(Date(parentId));
579 | } else {
580 | console.error(pks[i]);
581 | assert(false, "Unhandled column type in foreign key handling");
582 | }
583 |
584 | return whereCol + " = " + whereValue;
585 | }
586 |
587 | prepareRoute(internal, httpType, apiPrefix, urlRoute, routeType) {
588 | let route = {};
589 | route["httpType"] = httpType;
590 | route["routeUrl"] = apiPrefix + urlRoute;
591 | if (internal) {
592 | route["routeType"] = routeType;
593 | }
594 | return route;
595 | }
596 |
597 | getSchemaRoutes(internal, apiPrefix) {
598 | let schemaRoutes = [];
599 |
600 | for (var tableName in this.metaDb.tables) {
601 | if (tableName in this.sqlConfig.ignoreTables) {
602 | //console.log('ignore table', tableName);
603 | } else {
604 | let routes = [];
605 | let tableObj = {};
606 | let table = this.metaDb.tables[tableName];
607 | let isView = this.metaDb.tables[tableName]["isView"];
608 |
609 | tableObj["resource"] = tableName;
610 |
611 | // order of routes is important for express routing - DO NOT CHANGE ORDER
612 | routes.push(
613 | this.prepareRoute(
614 | internal,
615 | "get",
616 | apiPrefix,
617 | tableName + "/describe",
618 | "describe"
619 | )
620 | );
621 | routes.push(
622 | this.prepareRoute(
623 | internal,
624 | "get",
625 | apiPrefix,
626 | tableName + "/count",
627 | "count"
628 | )
629 | );
630 | routes.push(
631 | this.prepareRoute(
632 | internal,
633 | "get",
634 | apiPrefix,
635 | tableName + "/groupby",
636 | "groupby"
637 | )
638 | );
639 | routes.push(
640 | this.prepareRoute(
641 | internal,
642 | "get",
643 | apiPrefix,
644 | tableName + "/distinct",
645 | "distinct"
646 | )
647 | );
648 | routes.push(
649 | this.prepareRoute(
650 | internal,
651 | "get",
652 | apiPrefix,
653 | tableName + "/ugroupby",
654 | "ugroupby"
655 | )
656 | );
657 | routes.push(
658 | this.prepareRoute(
659 | internal,
660 | "get",
661 | apiPrefix,
662 | tableName + "/chart",
663 | "chart"
664 | )
665 | );
666 | routes.push(
667 | this.prepareRoute(
668 | internal,
669 | "get",
670 | apiPrefix,
671 | tableName + "/aggregate",
672 | "aggregate"
673 | )
674 | );
675 | routes.push(
676 | this.prepareRoute(
677 | internal,
678 | "get",
679 | apiPrefix,
680 | tableName + "/findOne",
681 | "findOne"
682 | )
683 | );
684 | routes.push(
685 | this.prepareRoute(
686 | internal,
687 | "get",
688 | apiPrefix,
689 | tableName + "/autoChart",
690 | "autoChart"
691 | )
692 | );
693 |
694 | if (!isView && !this.sqlConfig.readOnly) {
695 | routes.push(
696 | this.prepareRoute(internal, "post", apiPrefix, tableName, "create")
697 | );
698 | }
699 | routes.push(
700 | this.prepareRoute(internal, "get", apiPrefix, tableName, "list")
701 | );
702 |
703 | if (!isView && !this.sqlConfig.readOnly) {
704 | routes.push(
705 | this.prepareRoute(
706 | internal,
707 | "post",
708 | apiPrefix,
709 | tableName + "/bulk",
710 | "bulkInsert"
711 | )
712 | );
713 | routes.push(
714 | this.prepareRoute(
715 | internal,
716 | "delete",
717 | apiPrefix,
718 | tableName + "/bulk",
719 | "bulkDelete"
720 | )
721 | );
722 | }
723 | routes.push(
724 | this.prepareRoute(
725 | internal,
726 | "get",
727 | apiPrefix,
728 | tableName + "/bulk",
729 | "bulkRead"
730 | )
731 | );
732 |
733 | if (!isView && !this.sqlConfig.readOnly) {
734 | routes.push(
735 | this.prepareRoute(internal, "put", apiPrefix, tableName, "update")
736 | );
737 | routes.push(
738 | this.prepareRoute(
739 | internal,
740 | "patch",
741 | apiPrefix,
742 | tableName + "/:id",
743 | "patch"
744 | )
745 | );
746 | routes.push(
747 | this.prepareRoute(
748 | internal,
749 | "delete",
750 | apiPrefix,
751 | tableName + "/:id",
752 | "delete"
753 | )
754 | );
755 | }
756 |
757 | routes.push(
758 | this.prepareRoute(
759 | internal,
760 | "get",
761 | apiPrefix,
762 | tableName + "/:id",
763 | "read"
764 | )
765 | );
766 | routes.push(
767 | this.prepareRoute(
768 | internal,
769 | "get",
770 | apiPrefix,
771 | tableName + "/:id/exists",
772 | "exists"
773 | )
774 | );
775 |
776 | for (var j = 0; j < table["foreignKeys"].length; ++j) {
777 | let fk = table["foreignKeys"][j];
778 |
779 | if (fk["referenced_table_name"] in this.sqlConfig.ignoreTables) {
780 | //console.log('ignore table',fk['referenced_table_name']);
781 | } else {
782 | routes.push(
783 | this.prepareRoute(
784 | internal,
785 | "get",
786 | apiPrefix,
787 | fk["referenced_table_name"] + "/:id/" + fk["table_name"],
788 | "relational"
789 | )
790 | );
791 | }
792 | }
793 |
794 | var procList = this.getProcList()
795 | for (var j = 0; j < procList.length; j++) {
796 | routes.push(this.prepareRoute(internal, 'post', apiPrefix, '_proc/' + procList[j]))
797 | }
798 |
799 | tableObj['routes'] = routes;
800 |
801 | schemaRoutes.push(tableObj);
802 | }
803 | }
804 |
805 | return schemaRoutes;
806 | }
807 |
808 | getProcList() {
809 | let procRoutes = []
810 | for (let procName in this.metaDb.routines) {
811 | procRoutes.push(this.metaDb.routines[procName])
812 | }
813 | return procRoutes
814 | }
815 |
816 | getJoinType(joinInQueryParams) {
817 | //console.log('joinInQueryParams',joinInQueryParams);
818 |
819 | switch (joinInQueryParams) {
820 | case "_lj":
821 | return " left join ";
822 | break;
823 |
824 | case "_rj":
825 | return " right join ";
826 | break;
827 |
828 | // case '_fj':
829 | // return ' full join '
830 | // break;
831 |
832 | case "_ij":
833 | return " inner join ";
834 | break;
835 |
836 | case "_j":
837 | return " join ";
838 | break;
839 | }
840 |
841 | return " join ";
842 | }
843 |
844 | globalRoutesPrint(apiPrefix) {
845 | let r = [];
846 |
847 | r.push(apiPrefix + "tables");
848 | r.push(apiPrefix + "xjoin");
849 |
850 | if (this.sqlConfig.dynamic) {
851 | r.push(apiPrefix + "dynamic");
852 | r.push("/upload");
853 | r.push("/uploads");
854 | r.push("/download");
855 | }
856 |
857 | return r;
858 | }
859 |
860 | getChartQueryAndParamsFromStepPair(
861 | tableName,
862 | columnName,
863 | stepArray,
864 | isRange = false
865 | ) {
866 | let obj = {};
867 |
868 | obj.query = "";
869 | obj.params = [];
870 |
871 | //console.log('getChartQueryAndParamsFromStepArray',isRange);
872 |
873 | //select ? as ??, count(*) as _count from ?? where ?? between ? and ?
874 |
875 | if (
876 | stepArray.length &&
877 | stepArray.length >= 2 &&
878 | stepArray.length % 2 === 0
879 | ) {
880 | for (
881 | let i = 0;
882 | i < stepArray.length && stepArray.length >= 2;
883 | i = i + 2
884 | ) {
885 | obj.query = obj.query + dataHelp.getChartQuery();
886 |
887 | if (i + 2 < stepArray.length) {
888 | obj.query = obj.query + " union ";
889 | }
890 |
891 | obj.params.push(stepArray[i] + " to " + stepArray[i + 1]);
892 | obj.params.push(columnName);
893 | obj.params.push(tableName);
894 | obj.params.push(columnName);
895 | obj.params.push(stepArray[i]);
896 | obj.params.push(stepArray[i + 1]);
897 | }
898 | }
899 |
900 | //console.log('step spread query', obj);
901 |
902 | return obj;
903 | }
904 |
905 | getChartQueryAndParamsFromStepArray(
906 | tableName,
907 | columnName,
908 | stepArray,
909 | isRange = false
910 | ) {
911 | let obj = {};
912 |
913 | obj.query = "";
914 | obj.params = [];
915 |
916 | //console.log('getChartQueryAndParamsFromStepArray',isRange);
917 |
918 | if (stepArray.length && stepArray.length >= 2) {
919 | for (let i = 0; i < stepArray.length - 1; i = i + 1) {
920 | obj.query = obj.query + dataHelp.getChartQuery();
921 | if (i + 2 < stepArray.length) {
922 | obj.query = obj.query + " union ";
923 | }
924 |
925 | if (i && isRange === false) {
926 | stepArray[i] = stepArray[i] + 1;
927 | }
928 |
929 | if (isRange === false) {
930 | obj.params.push(stepArray[i] + " to " + stepArray[i + 1]);
931 | } else {
932 | obj.params.push(stepArray[0] + " to " + stepArray[i + 1]);
933 | }
934 |
935 | obj.params.push(columnName);
936 | obj.params.push(tableName);
937 | obj.params.push(columnName);
938 |
939 | if (isRange === false) {
940 | obj.params.push(stepArray[i]);
941 | obj.params.push(stepArray[i + 1]);
942 | } else {
943 | obj.params.push(stepArray[0]);
944 | obj.params.push(stepArray[i + 1]);
945 | }
946 | }
947 | }
948 |
949 | //console.log('step spread query', obj);
950 |
951 | return obj;
952 | }
953 |
954 | getChartQueryAndParamsFromMinMaxStddev(
955 | tableName,
956 | columnName,
957 | min,
958 | max,
959 | stddev,
960 | isRange = false
961 | ) {
962 | let stepArray = dataHelp.getStepArray(min, max, stddev);
963 |
964 | //console.log('steparray', stepArray);
965 |
966 | let obj = this.getChartQueryAndParamsFromStepArray(
967 | tableName,
968 | columnName,
969 | stepArray,
970 | isRange
971 | );
972 |
973 | //console.log('steparray', obj);
974 |
975 | return obj;
976 | }
977 |
978 | getChartQueryAndParamsFromMinMaxStep(
979 | tableName,
980 | columnName,
981 | min,
982 | max,
983 | step,
984 | isRange = false
985 | ) {
986 | let stepArray = dataHelp.getStepArraySimple(min, max, step);
987 |
988 | //console.log('steparray', stepArray);
989 |
990 | let obj = this.getChartQueryAndParamsFromStepArray(
991 | tableName,
992 | columnName,
993 | stepArray,
994 | isRange
995 | );
996 |
997 | //console.log('steparray', obj);
998 |
999 | return obj;
1000 | }
1001 |
1002 | _getGrpByHavingOrderBy(req, tableName, queryParamsObj, listType) {
1003 | /**************** add group by ****************/
1004 | this.getGroupByClause(
1005 | req.query._groupby,
1006 | req.app.locals._tableName,
1007 | queryParamsObj
1008 | );
1009 |
1010 | /**************** add having ****************/
1011 | this.getHavingClause(
1012 | req.query._having,
1013 | req.app.locals._tableName,
1014 | queryParamsObj
1015 | );
1016 |
1017 | /**************** add order clause ****************/
1018 | this.getOrderByClause(req.query, req.app.locals._tableName, queryParamsObj);
1019 |
1020 | /**************** add limit clause ****************/
1021 | if (listType === 2) {
1022 | //nested
1023 | queryParamsObj.query += " limit 1 ";
1024 | } else {
1025 | queryParamsObj.query += " limit ?,? ";
1026 | queryParamsObj.params = queryParamsObj.params.concat(
1027 | this.getLimitClause(req.query)
1028 | );
1029 | }
1030 | }
1031 |
1032 | /**
1033 | *
1034 | * @param req
1035 | * @param res
1036 | * @param queryParamsObj : {query, params}
1037 | * @param listType : 0:list, 1:nested, 2:findOne, 3:bulkRead, 4:distinct, 5:xjoin
1038 | *
1039 | * Updates query, params for query of type listType
1040 | */
1041 | prepareListQuery(req, res, queryParamsObj, listType = 0) {
1042 | queryParamsObj.query = "select ";
1043 | queryParamsObj.params = [];
1044 |
1045 | if (listType === 4) {
1046 | //list type distinct
1047 | queryParamsObj.query += " distinct ";
1048 | }
1049 |
1050 | /**************** select columns ****************/
1051 | if (req.query._groupby) {
1052 | this.getColumnsForSelectStmtWithGrpBy(
1053 | req.query,
1054 | req.app.locals._tableName,
1055 | queryParamsObj
1056 | );
1057 | } else {
1058 | this.getColumnsForSelectStmt(
1059 | req.app.locals._tableName,
1060 | req.query,
1061 | queryParamsObj
1062 | );
1063 | }
1064 |
1065 | /**************** add tableName ****************/
1066 | queryParamsObj.query += " from ?? ";
1067 |
1068 | if (listType === 1) {
1069 | //nested list
1070 |
1071 | req.app.locals._tableName = req.app.locals._childTable;
1072 |
1073 | queryParamsObj.params.push(req.app.locals._childTable);
1074 |
1075 | queryParamsObj.query += " where ";
1076 |
1077 | /**************** add where foreign key ****************/
1078 | let whereClause = this.getForeignKeyWhereClause(
1079 | req.app.locals._parentTable,
1080 | req.params.id,
1081 | req.app.locals._childTable
1082 | );
1083 |
1084 | if (!whereClause) {
1085 | return res.status(400).send({
1086 | error:
1087 | "Table is made of composite primary keys - all keys were not in input"
1088 | });
1089 | }
1090 | queryParamsObj.query += whereClause;
1091 |
1092 | this.getWhereClause(
1093 | req.query._where,
1094 | req.app.locals._tableName,
1095 | queryParamsObj,
1096 | " and "
1097 | );
1098 | } else if (listType === 3) {
1099 | //bulkRead
1100 |
1101 | // select * from table where pk in (ids) and whereConditions
1102 | queryParamsObj.params.push(req.app.locals._tableName);
1103 | queryParamsObj.query += " where ?? in ";
1104 | queryParamsObj.params.push(
1105 | this.getPrimaryKeyName(req.app.locals._tableName)
1106 | );
1107 |
1108 | queryParamsObj.query += "(";
1109 |
1110 | if (req.query && req.query._ids) {
1111 | let ids = req.query._ids.split(",");
1112 | for (var i = 0; i < ids.length; ++i) {
1113 | if (i) {
1114 | queryParamsObj.query += ",";
1115 | }
1116 | queryParamsObj.query += "?";
1117 | queryParamsObj.params.push(ids[i]);
1118 | }
1119 | }
1120 | queryParamsObj.query += ") ";
1121 | this.getWhereClause(
1122 | req.query._where,
1123 | req.app.locals._tableName,
1124 | queryParamsObj,
1125 | " and "
1126 | );
1127 | } else {
1128 | queryParamsObj.params.push(req.app.locals._tableName);
1129 |
1130 | /**************** add where clause ****************/
1131 | this.getWhereClause(
1132 | req.query._where,
1133 | req.app.locals._tableName,
1134 | queryParamsObj,
1135 | " where "
1136 | );
1137 | }
1138 |
1139 | this._getGrpByHavingOrderBy(req, req.app.locals._tableName, queryParamsObj);
1140 |
1141 | //console.log(queryParamsObj.query, queryParamsObj.params);
1142 | }
1143 |
1144 | _joinTableNames(isSecondJoin, joinTables, index, queryParamsObj) {
1145 | if (isSecondJoin) {
1146 | /**
1147 | * in second join - there will be ONE table and an ON condition
1148 | * if clause deals with this
1149 | *
1150 | */
1151 |
1152 | // add : join / left join / right join / full join / inner join
1153 | queryParamsObj.query += this.getJoinType(joinTables[index]);
1154 | queryParamsObj.query += " ?? as ?? ";
1155 |
1156 | // eg: tbl.tableName
1157 | let tableNameAndAs = joinTables[index + 1].split(".");
1158 |
1159 | if (
1160 | tableNameAndAs.length === 2 &&
1161 | !(tableNameAndAs[1] in this.sqlConfig.ignoreTables)
1162 | ) {
1163 | queryParamsObj.params.push(tableNameAndAs[1]);
1164 | queryParamsObj.params.push(tableNameAndAs[0]);
1165 | } else {
1166 | queryParamsObj.grammarErr = 1;
1167 | console.log("there was no dot for tableName ", joinTables[index + 1]);
1168 | }
1169 | } else {
1170 | /**
1171 | * in first join - there will be TWO tables and an ON condition
1172 | * else clause deals with this
1173 | */
1174 |
1175 | // first table
1176 | queryParamsObj.query += " ?? as ?? ";
1177 | // add : join / left join / right join / full join / inner join
1178 | queryParamsObj.query += this.getJoinType(joinTables[index + 1]);
1179 | // second table
1180 | queryParamsObj.query += " ?? as ?? ";
1181 |
1182 | let tableNameAndAs = joinTables[index].split(".");
1183 | if (
1184 | tableNameAndAs.length === 2 &&
1185 | !(tableNameAndAs[1] in this.sqlConfig.ignoreTables)
1186 | ) {
1187 | queryParamsObj.params.push(tableNameAndAs[1]);
1188 | queryParamsObj.params.push(tableNameAndAs[0]);
1189 | } else {
1190 | queryParamsObj.grammarErr = 1;
1191 | console.log("there was no dot for tableName ", joinTables[index]);
1192 | }
1193 |
1194 | tableNameAndAs = [];
1195 | tableNameAndAs = joinTables[index + 2].split(".");
1196 | if (
1197 | tableNameAndAs.length === 2 &&
1198 | !(tableNameAndAs[1] in this.sqlConfig.ignoreTables)
1199 | ) {
1200 | queryParamsObj.params.push(tableNameAndAs[1]);
1201 | queryParamsObj.params.push(tableNameAndAs[0]);
1202 | } else {
1203 | queryParamsObj.grammarErr = 1;
1204 | console.log("there was no dot for tableName ", joinTables[index]);
1205 | }
1206 | }
1207 | }
1208 |
1209 | prepareJoinQuery(req, res, queryParamsObj) {
1210 | queryParamsObj.query = "SELECT ";
1211 | queryParamsObj.grammarErr = 0;
1212 |
1213 | while (1) {
1214 | /**************** START : get fields ****************/
1215 | if (req.query._fields) {
1216 | let fields = req.query._fields.split(",");
1217 |
1218 | // from _fields to - ??, ??, ?? [col1,col2,col3]
1219 | for (var i = 0; i < fields.length && !queryParamsObj.grammarErr; ++i) {
1220 | if (i) {
1221 | queryParamsObj.query += ",";
1222 | }
1223 | queryParamsObj.query += " ?? ";
1224 | queryParamsObj.params.push(fields[i]);
1225 | let aliases = fields[i].split(".");
1226 | if (aliases.length === 2) {
1227 | queryParamsObj.query += "as " + aliases[0] + "_" + aliases[1];
1228 | //console.log(queryParamsObj.query);
1229 | } else {
1230 | queryParamsObj.grammarErr = 1;
1231 | }
1232 | }
1233 | } else {
1234 | queryParamsObj.grammarErr = 1;
1235 | }
1236 |
1237 | queryParamsObj.query += " from ";
1238 |
1239 | if (queryParamsObj.grammarErr) {
1240 | break;
1241 | }
1242 |
1243 | /**************** END : get fields ****************/
1244 |
1245 | /**************** START : get join + on ****************/
1246 | let joinTables = req.query._join.split(",");
1247 | if (joinTables.length < 3) {
1248 | //console.log('grammar error ', joinTables.length);
1249 | queryParamsObj.grammarErr = 1;
1250 | break;
1251 | }
1252 |
1253 | //console.log('jointables.length', joinTables);
1254 |
1255 | let onCondnCount = 0;
1256 |
1257 | for (
1258 | let i = 0;
1259 | i < joinTables.length - 1 && queryParamsObj.grammarErr === 0;
1260 | i = i + 2
1261 | ) {
1262 | onCondnCount++;
1263 |
1264 | this._joinTableNames(i, joinTables, i, queryParamsObj);
1265 |
1266 | if (queryParamsObj.grammarErr) {
1267 | console.log("failed at _joinTableNames", queryParamsObj);
1268 | break;
1269 | }
1270 |
1271 | //console.log('after join tables', queryParamsObj);
1272 |
1273 | let onCondn = "_on" + onCondnCount;
1274 | let onCondnObj = {};
1275 | if (onCondn in req.query) {
1276 | //console.log(onCondn, req.query[onCondn]);
1277 | onCondnObj = whereHelp.getConditionClause(req.query[onCondn], " on ");
1278 | //console.log('onCondnObj', onCondnObj);
1279 | queryParamsObj.query += " on " + onCondnObj.query;
1280 | queryParamsObj.params = queryParamsObj.params.concat(
1281 | onCondnObj.params
1282 | );
1283 | } else {
1284 | queryParamsObj.grammarErr = 1;
1285 | //console.log('No on condition: ', onCondn);
1286 | break;
1287 | }
1288 |
1289 | if (i === 0) {
1290 | i = i + 1;
1291 | }
1292 | }
1293 | /**************** END : get join + on ****************/
1294 |
1295 | if (queryParamsObj.grammarErr) {
1296 | break;
1297 | } else {
1298 | this.getWhereClause(
1299 | req.query._where,
1300 | " ignore ",
1301 | queryParamsObj,
1302 | " where "
1303 | );
1304 | this._getGrpByHavingOrderBy(req, "ignore", queryParamsObj, 5);
1305 | //console.log('after where',queryParamsObj);
1306 | }
1307 |
1308 | break;
1309 | }
1310 |
1311 | if (queryParamsObj.grammarErr) {
1312 | queryParamsObj.query = "";
1313 | queryParamsObj.params = [];
1314 | }
1315 |
1316 | return queryParamsObj;
1317 | }
1318 | }
1319 |
1320 | //expose class
1321 | module.exports = Xsql;
1322 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xmysql",
3 | "version": "0.6.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "accepts": {
8 | "version": "1.3.4",
9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
10 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=",
11 | "requires": {
12 | "mime-types": "~2.1.16",
13 | "negotiator": "0.6.1"
14 | }
15 | },
16 | "append-field": {
17 | "version": "0.1.0",
18 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz",
19 | "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo="
20 | },
21 | "array-flatten": {
22 | "version": "1.1.1",
23 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
24 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
25 | },
26 | "assert": {
27 | "version": "1.4.1",
28 | "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
29 | "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
30 | "requires": {
31 | "util": "0.10.3"
32 | }
33 | },
34 | "asynckit": {
35 | "version": "0.4.0",
36 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
37 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
38 | "dev": true
39 | },
40 | "balanced-match": {
41 | "version": "1.0.0",
42 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
43 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
44 | "dev": true
45 | },
46 | "basic-auth": {
47 | "version": "2.0.1",
48 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
49 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
50 | "requires": {
51 | "safe-buffer": "5.1.2"
52 | },
53 | "dependencies": {
54 | "safe-buffer": {
55 | "version": "5.1.2",
56 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
57 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
58 | }
59 | }
60 | },
61 | "bignumber.js": {
62 | "version": "9.0.0",
63 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
64 | "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
65 | },
66 | "body-parser": {
67 | "version": "1.18.2",
68 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
69 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
70 | "requires": {
71 | "bytes": "3.0.0",
72 | "content-type": "~1.0.4",
73 | "debug": "2.6.9",
74 | "depd": "~1.1.1",
75 | "http-errors": "~1.6.2",
76 | "iconv-lite": "0.4.19",
77 | "on-finished": "~2.3.0",
78 | "qs": "6.5.1",
79 | "raw-body": "2.3.2",
80 | "type-is": "~1.6.15"
81 | }
82 | },
83 | "brace-expansion": {
84 | "version": "1.1.11",
85 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
86 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
87 | "dev": true,
88 | "requires": {
89 | "balanced-match": "^1.0.0",
90 | "concat-map": "0.0.1"
91 | }
92 | },
93 | "browser-stdout": {
94 | "version": "1.3.1",
95 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
96 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
97 | "dev": true
98 | },
99 | "busboy": {
100 | "version": "0.2.14",
101 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
102 | "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
103 | "requires": {
104 | "dicer": "0.2.5",
105 | "readable-stream": "1.1.x"
106 | },
107 | "dependencies": {
108 | "isarray": {
109 | "version": "0.0.1",
110 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
111 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
112 | },
113 | "readable-stream": {
114 | "version": "1.1.14",
115 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
116 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
117 | "requires": {
118 | "core-util-is": "~1.0.0",
119 | "inherits": "~2.0.1",
120 | "isarray": "0.0.1",
121 | "string_decoder": "~0.10.x"
122 | }
123 | },
124 | "string_decoder": {
125 | "version": "0.10.31",
126 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
127 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
128 | }
129 | }
130 | },
131 | "bytes": {
132 | "version": "3.0.0",
133 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
134 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
135 | },
136 | "cluster": {
137 | "version": "0.7.7",
138 | "resolved": "https://registry.npmjs.org/cluster/-/cluster-0.7.7.tgz",
139 | "integrity": "sha1-5JfiZ8yVa9CwUTrbSqOTNX0Ahe8=",
140 | "requires": {
141 | "log": ">= 1.2.0",
142 | "mkdirp": ">= 0.0.1"
143 | }
144 | },
145 | "colors": {
146 | "version": "1.1.2",
147 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
148 | "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
149 | },
150 | "combined-stream": {
151 | "version": "1.0.5",
152 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
153 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
154 | "dev": true,
155 | "requires": {
156 | "delayed-stream": "~1.0.0"
157 | }
158 | },
159 | "commander": {
160 | "version": "2.11.0",
161 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
162 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
163 | },
164 | "component-emitter": {
165 | "version": "1.2.1",
166 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
167 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
168 | "dev": true
169 | },
170 | "concat-map": {
171 | "version": "0.0.1",
172 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
173 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
174 | "dev": true
175 | },
176 | "concat-stream": {
177 | "version": "1.6.0",
178 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
179 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
180 | "requires": {
181 | "inherits": "^2.0.3",
182 | "readable-stream": "^2.2.2",
183 | "typedarray": "^0.0.6"
184 | }
185 | },
186 | "contains-path": {
187 | "version": "0.1.0",
188 | "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
189 | "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
190 | "dev": true
191 | },
192 | "content-disposition": {
193 | "version": "0.5.2",
194 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
195 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
196 | },
197 | "content-type": {
198 | "version": "1.0.4",
199 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
200 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
201 | },
202 | "cookie": {
203 | "version": "0.3.1",
204 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
205 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
206 | },
207 | "cookie-signature": {
208 | "version": "1.0.6",
209 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
210 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
211 | },
212 | "cookiejar": {
213 | "version": "2.1.1",
214 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz",
215 | "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=",
216 | "dev": true
217 | },
218 | "core-util-is": {
219 | "version": "1.0.2",
220 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
221 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
222 | },
223 | "cors": {
224 | "version": "2.8.4",
225 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz",
226 | "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=",
227 | "requires": {
228 | "object-assign": "^4",
229 | "vary": "^1"
230 | },
231 | "dependencies": {
232 | "object-assign": {
233 | "version": "4.1.1",
234 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
235 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
236 | }
237 | }
238 | },
239 | "debug": {
240 | "version": "2.6.9",
241 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
242 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
243 | "requires": {
244 | "ms": "2.0.0"
245 | }
246 | },
247 | "define-properties": {
248 | "version": "1.1.3",
249 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
250 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
251 | "dev": true,
252 | "requires": {
253 | "object-keys": "^1.0.12"
254 | }
255 | },
256 | "delayed-stream": {
257 | "version": "1.0.0",
258 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
259 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
260 | "dev": true
261 | },
262 | "depd": {
263 | "version": "1.1.1",
264 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
265 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
266 | },
267 | "destroy": {
268 | "version": "1.0.4",
269 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
270 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
271 | },
272 | "dicer": {
273 | "version": "0.2.5",
274 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
275 | "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
276 | "requires": {
277 | "readable-stream": "1.1.x",
278 | "streamsearch": "0.1.2"
279 | },
280 | "dependencies": {
281 | "isarray": {
282 | "version": "0.0.1",
283 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
284 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
285 | },
286 | "readable-stream": {
287 | "version": "1.1.14",
288 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
289 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
290 | "requires": {
291 | "core-util-is": "~1.0.0",
292 | "inherits": "~2.0.1",
293 | "isarray": "0.0.1",
294 | "string_decoder": "~0.10.x"
295 | }
296 | },
297 | "string_decoder": {
298 | "version": "0.10.31",
299 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
300 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
301 | }
302 | }
303 | },
304 | "diff": {
305 | "version": "3.5.0",
306 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
307 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
308 | "dev": true
309 | },
310 | "doctrine": {
311 | "version": "1.5.0",
312 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
313 | "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
314 | "dev": true,
315 | "requires": {
316 | "esutils": "^2.0.2",
317 | "isarray": "^1.0.0"
318 | }
319 | },
320 | "ee-first": {
321 | "version": "1.1.1",
322 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
323 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
324 | },
325 | "encodeurl": {
326 | "version": "1.0.1",
327 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
328 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
329 | },
330 | "error-ex": {
331 | "version": "1.3.2",
332 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
333 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
334 | "dev": true,
335 | "requires": {
336 | "is-arrayish": "^0.2.1"
337 | }
338 | },
339 | "es-abstract": {
340 | "version": "1.13.0",
341 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
342 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
343 | "dev": true,
344 | "requires": {
345 | "es-to-primitive": "^1.2.0",
346 | "function-bind": "^1.1.1",
347 | "has": "^1.0.3",
348 | "is-callable": "^1.1.4",
349 | "is-regex": "^1.0.4",
350 | "object-keys": "^1.0.12"
351 | }
352 | },
353 | "es-to-primitive": {
354 | "version": "1.2.0",
355 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
356 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
357 | "dev": true,
358 | "requires": {
359 | "is-callable": "^1.1.4",
360 | "is-date-object": "^1.0.1",
361 | "is-symbol": "^1.0.2"
362 | }
363 | },
364 | "escape-html": {
365 | "version": "1.0.3",
366 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
367 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
368 | },
369 | "escape-string-regexp": {
370 | "version": "1.0.5",
371 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
372 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
373 | "dev": true
374 | },
375 | "eslint-config-airbnb-base": {
376 | "version": "13.1.0",
377 | "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz",
378 | "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==",
379 | "dev": true,
380 | "requires": {
381 | "eslint-restricted-globals": "^0.1.1",
382 | "object.assign": "^4.1.0",
383 | "object.entries": "^1.0.4"
384 | }
385 | },
386 | "eslint-config-prettier": {
387 | "version": "4.1.0",
388 | "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-4.1.0.tgz",
389 | "integrity": "sha512-zILwX9/Ocz4SV2vX7ox85AsrAgXV3f2o2gpIicdMIOra48WYqgUnWNH/cR/iHtmD2Vb3dLSC3LiEJnS05Gkw7w==",
390 | "dev": true,
391 | "requires": {
392 | "get-stdin": "^6.0.0"
393 | }
394 | },
395 | "eslint-import-resolver-node": {
396 | "version": "0.3.2",
397 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
398 | "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
399 | "dev": true,
400 | "requires": {
401 | "debug": "^2.6.9",
402 | "resolve": "^1.5.0"
403 | }
404 | },
405 | "eslint-module-utils": {
406 | "version": "2.3.0",
407 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz",
408 | "integrity": "sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==",
409 | "dev": true,
410 | "requires": {
411 | "debug": "^2.6.8",
412 | "pkg-dir": "^2.0.0"
413 | }
414 | },
415 | "eslint-plugin-import": {
416 | "version": "2.16.0",
417 | "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz",
418 | "integrity": "sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==",
419 | "dev": true,
420 | "requires": {
421 | "contains-path": "^0.1.0",
422 | "debug": "^2.6.9",
423 | "doctrine": "1.5.0",
424 | "eslint-import-resolver-node": "^0.3.2",
425 | "eslint-module-utils": "^2.3.0",
426 | "has": "^1.0.3",
427 | "lodash": "^4.17.11",
428 | "minimatch": "^3.0.4",
429 | "read-pkg-up": "^2.0.0",
430 | "resolve": "^1.9.0"
431 | }
432 | },
433 | "eslint-plugin-prettier": {
434 | "version": "3.0.1",
435 | "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.1.tgz",
436 | "integrity": "sha512-/PMttrarPAY78PLvV3xfWibMOdMDl57hmlQ2XqFeA37wd+CJ7WSxV7txqjVPHi/AAFKd2lX0ZqfsOc/i5yFCSQ==",
437 | "dev": true,
438 | "requires": {
439 | "prettier-linter-helpers": "^1.0.0"
440 | }
441 | },
442 | "eslint-restricted-globals": {
443 | "version": "0.1.1",
444 | "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz",
445 | "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
446 | "dev": true
447 | },
448 | "esutils": {
449 | "version": "2.0.2",
450 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
451 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
452 | "dev": true
453 | },
454 | "etag": {
455 | "version": "1.8.1",
456 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
457 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
458 | },
459 | "express": {
460 | "version": "4.16.1",
461 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.1.tgz",
462 | "integrity": "sha512-STB7LZ4N0L+81FJHGla2oboUHTk4PaN1RsOkoRh9OSeEKylvF5hwKYVX1xCLFaCT7MD0BNG/gX2WFMLqY6EMBw==",
463 | "requires": {
464 | "accepts": "~1.3.4",
465 | "array-flatten": "1.1.1",
466 | "body-parser": "1.18.2",
467 | "content-disposition": "0.5.2",
468 | "content-type": "~1.0.4",
469 | "cookie": "0.3.1",
470 | "cookie-signature": "1.0.6",
471 | "debug": "2.6.9",
472 | "depd": "~1.1.1",
473 | "encodeurl": "~1.0.1",
474 | "escape-html": "~1.0.3",
475 | "etag": "~1.8.1",
476 | "finalhandler": "1.1.0",
477 | "fresh": "0.5.2",
478 | "merge-descriptors": "1.0.1",
479 | "methods": "~1.1.2",
480 | "on-finished": "~2.3.0",
481 | "parseurl": "~1.3.2",
482 | "path-to-regexp": "0.1.7",
483 | "proxy-addr": "~2.0.2",
484 | "qs": "6.5.1",
485 | "range-parser": "~1.2.0",
486 | "safe-buffer": "5.1.1",
487 | "send": "0.16.1",
488 | "serve-static": "1.13.1",
489 | "setprototypeof": "1.1.0",
490 | "statuses": "~1.3.1",
491 | "type-is": "~1.6.15",
492 | "utils-merge": "1.0.1",
493 | "vary": "~1.1.2"
494 | }
495 | },
496 | "extend": {
497 | "version": "3.0.2",
498 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
499 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
500 | "dev": true
501 | },
502 | "fast-diff": {
503 | "version": "1.2.0",
504 | "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
505 | "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
506 | "dev": true
507 | },
508 | "finalhandler": {
509 | "version": "1.1.0",
510 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
511 | "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
512 | "requires": {
513 | "debug": "2.6.9",
514 | "encodeurl": "~1.0.1",
515 | "escape-html": "~1.0.3",
516 | "on-finished": "~2.3.0",
517 | "parseurl": "~1.3.2",
518 | "statuses": "~1.3.1",
519 | "unpipe": "~1.0.0"
520 | }
521 | },
522 | "find-up": {
523 | "version": "2.1.0",
524 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
525 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
526 | "dev": true,
527 | "requires": {
528 | "locate-path": "^2.0.0"
529 | }
530 | },
531 | "form-data": {
532 | "version": "2.3.1",
533 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
534 | "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
535 | "dev": true,
536 | "requires": {
537 | "asynckit": "^0.4.0",
538 | "combined-stream": "^1.0.5",
539 | "mime-types": "^2.1.12"
540 | }
541 | },
542 | "forwarded": {
543 | "version": "0.1.2",
544 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
545 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
546 | },
547 | "fresh": {
548 | "version": "0.5.2",
549 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
550 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
551 | },
552 | "fs.realpath": {
553 | "version": "1.0.0",
554 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
555 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
556 | "dev": true
557 | },
558 | "function-bind": {
559 | "version": "1.1.1",
560 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
561 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
562 | "dev": true
563 | },
564 | "get-stdin": {
565 | "version": "6.0.0",
566 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
567 | "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
568 | "dev": true
569 | },
570 | "glob": {
571 | "version": "7.1.2",
572 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
573 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
574 | "dev": true,
575 | "requires": {
576 | "fs.realpath": "^1.0.0",
577 | "inflight": "^1.0.4",
578 | "inherits": "2",
579 | "minimatch": "^3.0.4",
580 | "once": "^1.3.0",
581 | "path-is-absolute": "^1.0.0"
582 | }
583 | },
584 | "graceful-fs": {
585 | "version": "4.1.15",
586 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
587 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
588 | "dev": true
589 | },
590 | "growl": {
591 | "version": "1.10.5",
592 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
593 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
594 | "dev": true
595 | },
596 | "has": {
597 | "version": "1.0.3",
598 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
599 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
600 | "dev": true,
601 | "requires": {
602 | "function-bind": "^1.1.1"
603 | }
604 | },
605 | "has-flag": {
606 | "version": "3.0.0",
607 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
608 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
609 | "dev": true
610 | },
611 | "has-symbols": {
612 | "version": "1.0.0",
613 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
614 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
615 | "dev": true
616 | },
617 | "he": {
618 | "version": "1.1.1",
619 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
620 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
621 | "dev": true
622 | },
623 | "hosted-git-info": {
624 | "version": "2.7.1",
625 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
626 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
627 | "dev": true
628 | },
629 | "http-errors": {
630 | "version": "1.6.2",
631 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
632 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
633 | "requires": {
634 | "depd": "1.1.1",
635 | "inherits": "2.0.3",
636 | "setprototypeof": "1.0.3",
637 | "statuses": ">= 1.3.1 < 2"
638 | },
639 | "dependencies": {
640 | "setprototypeof": {
641 | "version": "1.0.3",
642 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
643 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
644 | }
645 | }
646 | },
647 | "iconv-lite": {
648 | "version": "0.4.19",
649 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
650 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
651 | },
652 | "inflight": {
653 | "version": "1.0.6",
654 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
655 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
656 | "dev": true,
657 | "requires": {
658 | "once": "^1.3.0",
659 | "wrappy": "1"
660 | }
661 | },
662 | "inherits": {
663 | "version": "2.0.3",
664 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
665 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
666 | },
667 | "ipaddr.js": {
668 | "version": "1.5.2",
669 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz",
670 | "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A="
671 | },
672 | "is-arrayish": {
673 | "version": "0.2.1",
674 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
675 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
676 | "dev": true
677 | },
678 | "is-callable": {
679 | "version": "1.1.4",
680 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
681 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
682 | "dev": true
683 | },
684 | "is-date-object": {
685 | "version": "1.0.1",
686 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
687 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
688 | "dev": true
689 | },
690 | "is-regex": {
691 | "version": "1.0.4",
692 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
693 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
694 | "dev": true,
695 | "requires": {
696 | "has": "^1.0.1"
697 | }
698 | },
699 | "is-symbol": {
700 | "version": "1.0.2",
701 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
702 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
703 | "dev": true,
704 | "requires": {
705 | "has-symbols": "^1.0.0"
706 | }
707 | },
708 | "isarray": {
709 | "version": "1.0.0",
710 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
711 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
712 | },
713 | "load-json-file": {
714 | "version": "2.0.0",
715 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
716 | "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
717 | "dev": true,
718 | "requires": {
719 | "graceful-fs": "^4.1.2",
720 | "parse-json": "^2.2.0",
721 | "pify": "^2.0.0",
722 | "strip-bom": "^3.0.0"
723 | }
724 | },
725 | "locate-path": {
726 | "version": "2.0.0",
727 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
728 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
729 | "dev": true,
730 | "requires": {
731 | "p-locate": "^2.0.0",
732 | "path-exists": "^3.0.0"
733 | }
734 | },
735 | "lodash": {
736 | "version": "4.17.15",
737 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
738 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
739 | "dev": true
740 | },
741 | "log": {
742 | "version": "1.4.0",
743 | "resolved": "https://registry.npmjs.org/log/-/log-1.4.0.tgz",
744 | "integrity": "sha1-S6HYkP3iSbAx3KA7w36q8yVlbxw="
745 | },
746 | "media-typer": {
747 | "version": "0.3.0",
748 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
749 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
750 | },
751 | "merge-descriptors": {
752 | "version": "1.0.1",
753 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
754 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
755 | },
756 | "methods": {
757 | "version": "1.1.2",
758 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
759 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
760 | },
761 | "mime": {
762 | "version": "1.4.1",
763 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
764 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
765 | },
766 | "mime-db": {
767 | "version": "1.30.0",
768 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
769 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
770 | },
771 | "mime-types": {
772 | "version": "2.1.17",
773 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
774 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
775 | "requires": {
776 | "mime-db": "~1.30.0"
777 | }
778 | },
779 | "minimatch": {
780 | "version": "3.0.4",
781 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
782 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
783 | "dev": true,
784 | "requires": {
785 | "brace-expansion": "^1.1.7"
786 | }
787 | },
788 | "minimist": {
789 | "version": "0.0.8",
790 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
791 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
792 | },
793 | "mkdirp": {
794 | "version": "0.5.1",
795 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
796 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
797 | "requires": {
798 | "minimist": "0.0.8"
799 | }
800 | },
801 | "mocha": {
802 | "version": "5.2.0",
803 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
804 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
805 | "dev": true,
806 | "requires": {
807 | "browser-stdout": "1.3.1",
808 | "commander": "2.15.1",
809 | "debug": "3.1.0",
810 | "diff": "3.5.0",
811 | "escape-string-regexp": "1.0.5",
812 | "glob": "7.1.2",
813 | "growl": "1.10.5",
814 | "he": "1.1.1",
815 | "minimatch": "3.0.4",
816 | "mkdirp": "0.5.1",
817 | "supports-color": "5.4.0"
818 | },
819 | "dependencies": {
820 | "commander": {
821 | "version": "2.15.1",
822 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
823 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
824 | "dev": true
825 | },
826 | "debug": {
827 | "version": "3.1.0",
828 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
829 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
830 | "dev": true,
831 | "requires": {
832 | "ms": "2.0.0"
833 | }
834 | }
835 | }
836 | },
837 | "morgan": {
838 | "version": "1.9.1",
839 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
840 | "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
841 | "requires": {
842 | "basic-auth": "~2.0.0",
843 | "debug": "2.6.9",
844 | "depd": "~1.1.2",
845 | "on-finished": "~2.3.0",
846 | "on-headers": "~1.0.1"
847 | },
848 | "dependencies": {
849 | "depd": {
850 | "version": "1.1.2",
851 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
852 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
853 | }
854 | }
855 | },
856 | "ms": {
857 | "version": "2.0.0",
858 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
859 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
860 | },
861 | "multer": {
862 | "version": "1.3.0",
863 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz",
864 | "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=",
865 | "requires": {
866 | "append-field": "^0.1.0",
867 | "busboy": "^0.2.11",
868 | "concat-stream": "^1.5.0",
869 | "mkdirp": "^0.5.1",
870 | "object-assign": "^3.0.0",
871 | "on-finished": "^2.3.0",
872 | "type-is": "^1.6.4",
873 | "xtend": "^4.0.0"
874 | }
875 | },
876 | "mysql": {
877 | "version": "2.18.1",
878 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
879 | "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
880 | "requires": {
881 | "bignumber.js": "9.0.0",
882 | "readable-stream": "2.3.7",
883 | "safe-buffer": "5.1.2",
884 | "sqlstring": "2.3.1"
885 | },
886 | "dependencies": {
887 | "process-nextick-args": {
888 | "version": "2.0.1",
889 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
890 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
891 | },
892 | "readable-stream": {
893 | "version": "2.3.7",
894 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
895 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
896 | "requires": {
897 | "core-util-is": "~1.0.0",
898 | "inherits": "~2.0.3",
899 | "isarray": "~1.0.0",
900 | "process-nextick-args": "~2.0.0",
901 | "safe-buffer": "~5.1.1",
902 | "string_decoder": "~1.1.1",
903 | "util-deprecate": "~1.0.1"
904 | }
905 | },
906 | "safe-buffer": {
907 | "version": "5.1.2",
908 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
909 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
910 | },
911 | "string_decoder": {
912 | "version": "1.1.1",
913 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
914 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
915 | "requires": {
916 | "safe-buffer": "~5.1.0"
917 | }
918 | }
919 | }
920 | },
921 | "nan": {
922 | "version": "2.14.0",
923 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
924 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
925 | "dev": true
926 | },
927 | "negotiator": {
928 | "version": "0.6.1",
929 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
930 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
931 | },
932 | "normalize-package-data": {
933 | "version": "2.5.0",
934 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
935 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
936 | "dev": true,
937 | "requires": {
938 | "hosted-git-info": "^2.1.4",
939 | "resolve": "^1.10.0",
940 | "semver": "2 || 3 || 4 || 5",
941 | "validate-npm-package-license": "^3.0.1"
942 | }
943 | },
944 | "object-assign": {
945 | "version": "3.0.0",
946 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
947 | "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
948 | },
949 | "object-keys": {
950 | "version": "1.1.0",
951 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz",
952 | "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==",
953 | "dev": true
954 | },
955 | "object.assign": {
956 | "version": "4.1.0",
957 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
958 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
959 | "dev": true,
960 | "requires": {
961 | "define-properties": "^1.1.2",
962 | "function-bind": "^1.1.1",
963 | "has-symbols": "^1.0.0",
964 | "object-keys": "^1.0.11"
965 | }
966 | },
967 | "object.entries": {
968 | "version": "1.1.0",
969 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
970 | "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
971 | "dev": true,
972 | "requires": {
973 | "define-properties": "^1.1.3",
974 | "es-abstract": "^1.12.0",
975 | "function-bind": "^1.1.1",
976 | "has": "^1.0.3"
977 | }
978 | },
979 | "on-finished": {
980 | "version": "2.3.0",
981 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
982 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
983 | "requires": {
984 | "ee-first": "1.1.1"
985 | }
986 | },
987 | "on-headers": {
988 | "version": "1.0.2",
989 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
990 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
991 | },
992 | "once": {
993 | "version": "1.4.0",
994 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
995 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
996 | "dev": true,
997 | "requires": {
998 | "wrappy": "1"
999 | }
1000 | },
1001 | "p-limit": {
1002 | "version": "1.3.0",
1003 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
1004 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
1005 | "dev": true,
1006 | "requires": {
1007 | "p-try": "^1.0.0"
1008 | }
1009 | },
1010 | "p-locate": {
1011 | "version": "2.0.0",
1012 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
1013 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
1014 | "dev": true,
1015 | "requires": {
1016 | "p-limit": "^1.1.0"
1017 | }
1018 | },
1019 | "p-try": {
1020 | "version": "1.0.0",
1021 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
1022 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
1023 | "dev": true
1024 | },
1025 | "parse-json": {
1026 | "version": "2.2.0",
1027 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
1028 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
1029 | "dev": true,
1030 | "requires": {
1031 | "error-ex": "^1.2.0"
1032 | }
1033 | },
1034 | "parseurl": {
1035 | "version": "1.3.2",
1036 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
1037 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
1038 | },
1039 | "path-exists": {
1040 | "version": "3.0.0",
1041 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
1042 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
1043 | "dev": true
1044 | },
1045 | "path-is-absolute": {
1046 | "version": "1.0.1",
1047 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1048 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
1049 | "dev": true
1050 | },
1051 | "path-parse": {
1052 | "version": "1.0.6",
1053 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
1054 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
1055 | "dev": true
1056 | },
1057 | "path-to-regexp": {
1058 | "version": "0.1.7",
1059 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1060 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
1061 | },
1062 | "path-type": {
1063 | "version": "2.0.0",
1064 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
1065 | "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
1066 | "dev": true,
1067 | "requires": {
1068 | "pify": "^2.0.0"
1069 | }
1070 | },
1071 | "pify": {
1072 | "version": "2.3.0",
1073 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1074 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
1075 | "dev": true
1076 | },
1077 | "pkg-dir": {
1078 | "version": "2.0.0",
1079 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
1080 | "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
1081 | "dev": true,
1082 | "requires": {
1083 | "find-up": "^2.1.0"
1084 | }
1085 | },
1086 | "prettier-linter-helpers": {
1087 | "version": "1.0.0",
1088 | "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
1089 | "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
1090 | "dev": true,
1091 | "requires": {
1092 | "fast-diff": "^1.1.2"
1093 | }
1094 | },
1095 | "process-nextick-args": {
1096 | "version": "1.0.7",
1097 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
1098 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
1099 | },
1100 | "proxy-addr": {
1101 | "version": "2.0.2",
1102 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz",
1103 | "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=",
1104 | "requires": {
1105 | "forwarded": "~0.1.2",
1106 | "ipaddr.js": "1.5.2"
1107 | }
1108 | },
1109 | "qs": {
1110 | "version": "6.5.1",
1111 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
1112 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
1113 | },
1114 | "range-parser": {
1115 | "version": "1.2.0",
1116 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
1117 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
1118 | },
1119 | "raw-body": {
1120 | "version": "2.3.2",
1121 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
1122 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
1123 | "requires": {
1124 | "bytes": "3.0.0",
1125 | "http-errors": "1.6.2",
1126 | "iconv-lite": "0.4.19",
1127 | "unpipe": "1.0.0"
1128 | }
1129 | },
1130 | "read-pkg": {
1131 | "version": "2.0.0",
1132 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
1133 | "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
1134 | "dev": true,
1135 | "requires": {
1136 | "load-json-file": "^2.0.0",
1137 | "normalize-package-data": "^2.3.2",
1138 | "path-type": "^2.0.0"
1139 | }
1140 | },
1141 | "read-pkg-up": {
1142 | "version": "2.0.0",
1143 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
1144 | "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
1145 | "dev": true,
1146 | "requires": {
1147 | "find-up": "^2.0.0",
1148 | "read-pkg": "^2.0.0"
1149 | }
1150 | },
1151 | "readable-stream": {
1152 | "version": "2.3.3",
1153 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
1154 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
1155 | "requires": {
1156 | "core-util-is": "~1.0.0",
1157 | "inherits": "~2.0.3",
1158 | "isarray": "~1.0.0",
1159 | "process-nextick-args": "~1.0.6",
1160 | "safe-buffer": "~5.1.1",
1161 | "string_decoder": "~1.0.3",
1162 | "util-deprecate": "~1.0.1"
1163 | }
1164 | },
1165 | "resolve": {
1166 | "version": "1.10.0",
1167 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
1168 | "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
1169 | "dev": true,
1170 | "requires": {
1171 | "path-parse": "^1.0.6"
1172 | }
1173 | },
1174 | "safe-buffer": {
1175 | "version": "5.1.1",
1176 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
1177 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
1178 | },
1179 | "semver": {
1180 | "version": "5.7.0",
1181 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
1182 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
1183 | "dev": true
1184 | },
1185 | "send": {
1186 | "version": "0.16.1",
1187 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz",
1188 | "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==",
1189 | "requires": {
1190 | "debug": "2.6.9",
1191 | "depd": "~1.1.1",
1192 | "destroy": "~1.0.4",
1193 | "encodeurl": "~1.0.1",
1194 | "escape-html": "~1.0.3",
1195 | "etag": "~1.8.1",
1196 | "fresh": "0.5.2",
1197 | "http-errors": "~1.6.2",
1198 | "mime": "1.4.1",
1199 | "ms": "2.0.0",
1200 | "on-finished": "~2.3.0",
1201 | "range-parser": "~1.2.0",
1202 | "statuses": "~1.3.1"
1203 | }
1204 | },
1205 | "serve-static": {
1206 | "version": "1.13.1",
1207 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz",
1208 | "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==",
1209 | "requires": {
1210 | "encodeurl": "~1.0.1",
1211 | "escape-html": "~1.0.3",
1212 | "parseurl": "~1.3.2",
1213 | "send": "0.16.1"
1214 | }
1215 | },
1216 | "setprototypeof": {
1217 | "version": "1.1.0",
1218 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
1219 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
1220 | },
1221 | "should": {
1222 | "version": "13.1.2",
1223 | "resolved": "https://registry.npmjs.org/should/-/should-13.1.2.tgz",
1224 | "integrity": "sha512-oiGqKOuE4t98vdCs4ICifvzL2u9nWMaziSXVwHOYPyqqY1gBzGZS6LvzIc5uEFN0PiS69Sbvcqyw9hbYXkF4og==",
1225 | "dev": true,
1226 | "requires": {
1227 | "should-equal": "^2.0.0",
1228 | "should-format": "^3.0.3",
1229 | "should-type": "^1.4.0",
1230 | "should-type-adaptors": "^1.0.1",
1231 | "should-util": "^1.0.0"
1232 | }
1233 | },
1234 | "should-equal": {
1235 | "version": "2.0.0",
1236 | "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
1237 | "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
1238 | "dev": true,
1239 | "requires": {
1240 | "should-type": "^1.4.0"
1241 | }
1242 | },
1243 | "should-format": {
1244 | "version": "3.0.3",
1245 | "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
1246 | "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
1247 | "dev": true,
1248 | "requires": {
1249 | "should-type": "^1.3.0",
1250 | "should-type-adaptors": "^1.0.1"
1251 | }
1252 | },
1253 | "should-type": {
1254 | "version": "1.4.0",
1255 | "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
1256 | "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
1257 | "dev": true
1258 | },
1259 | "should-type-adaptors": {
1260 | "version": "1.0.1",
1261 | "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.0.1.tgz",
1262 | "integrity": "sha1-7+VVPN9oz/ZuXF9RtxLcNRx3vqo=",
1263 | "dev": true,
1264 | "requires": {
1265 | "should-type": "^1.3.0",
1266 | "should-util": "^1.0.0"
1267 | }
1268 | },
1269 | "should-util": {
1270 | "version": "1.0.0",
1271 | "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz",
1272 | "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=",
1273 | "dev": true
1274 | },
1275 | "sleep": {
1276 | "version": "6.1.0",
1277 | "resolved": "https://registry.npmjs.org/sleep/-/sleep-6.1.0.tgz",
1278 | "integrity": "sha512-Z1x4JjJxsru75Tqn8F4tnOFeEu3HjtITTsumYUiuz54sGKdISgLCek9AUlXlVVrkhltRFhNUsJDJE76SFHTDIQ==",
1279 | "dev": true,
1280 | "requires": {
1281 | "nan": "^2.13.2"
1282 | }
1283 | },
1284 | "spdx-correct": {
1285 | "version": "3.1.0",
1286 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
1287 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
1288 | "dev": true,
1289 | "requires": {
1290 | "spdx-expression-parse": "^3.0.0",
1291 | "spdx-license-ids": "^3.0.0"
1292 | }
1293 | },
1294 | "spdx-exceptions": {
1295 | "version": "2.2.0",
1296 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
1297 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
1298 | "dev": true
1299 | },
1300 | "spdx-expression-parse": {
1301 | "version": "3.0.0",
1302 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
1303 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
1304 | "dev": true,
1305 | "requires": {
1306 | "spdx-exceptions": "^2.1.0",
1307 | "spdx-license-ids": "^3.0.0"
1308 | }
1309 | },
1310 | "spdx-license-ids": {
1311 | "version": "3.0.3",
1312 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
1313 | "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==",
1314 | "dev": true
1315 | },
1316 | "sqlstring": {
1317 | "version": "2.3.1",
1318 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
1319 | "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
1320 | },
1321 | "statuses": {
1322 | "version": "1.3.1",
1323 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
1324 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
1325 | },
1326 | "streamsearch": {
1327 | "version": "0.1.2",
1328 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
1329 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
1330 | },
1331 | "string_decoder": {
1332 | "version": "1.0.3",
1333 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
1334 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
1335 | "requires": {
1336 | "safe-buffer": "~5.1.0"
1337 | }
1338 | },
1339 | "strip-bom": {
1340 | "version": "3.0.0",
1341 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
1342 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
1343 | "dev": true
1344 | },
1345 | "superagent": {
1346 | "version": "3.8.3",
1347 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
1348 | "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
1349 | "dev": true,
1350 | "requires": {
1351 | "component-emitter": "^1.2.0",
1352 | "cookiejar": "^2.1.0",
1353 | "debug": "^3.1.0",
1354 | "extend": "^3.0.0",
1355 | "form-data": "^2.3.1",
1356 | "formidable": "^1.2.0",
1357 | "methods": "^1.1.1",
1358 | "mime": "^1.4.1",
1359 | "qs": "^6.5.1",
1360 | "readable-stream": "^2.3.5"
1361 | },
1362 | "dependencies": {
1363 | "debug": {
1364 | "version": "3.2.6",
1365 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
1366 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
1367 | "dev": true,
1368 | "requires": {
1369 | "ms": "^2.1.1"
1370 | }
1371 | },
1372 | "formidable": {
1373 | "version": "1.2.1",
1374 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
1375 | "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==",
1376 | "dev": true
1377 | },
1378 | "ms": {
1379 | "version": "2.1.2",
1380 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1381 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1382 | "dev": true
1383 | },
1384 | "process-nextick-args": {
1385 | "version": "2.0.1",
1386 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
1387 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
1388 | "dev": true
1389 | },
1390 | "readable-stream": {
1391 | "version": "2.3.6",
1392 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
1393 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
1394 | "dev": true,
1395 | "requires": {
1396 | "core-util-is": "~1.0.0",
1397 | "inherits": "~2.0.3",
1398 | "isarray": "~1.0.0",
1399 | "process-nextick-args": "~2.0.0",
1400 | "safe-buffer": "~5.1.1",
1401 | "string_decoder": "~1.1.1",
1402 | "util-deprecate": "~1.0.1"
1403 | }
1404 | },
1405 | "string_decoder": {
1406 | "version": "1.1.1",
1407 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1408 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1409 | "dev": true,
1410 | "requires": {
1411 | "safe-buffer": "~5.1.0"
1412 | }
1413 | }
1414 | }
1415 | },
1416 | "supertest": {
1417 | "version": "3.0.0",
1418 | "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.0.0.tgz",
1419 | "integrity": "sha1-jUu2j9GDDuBwM7HFpamkAhyWUpY=",
1420 | "dev": true,
1421 | "requires": {
1422 | "methods": "~1.1.2",
1423 | "superagent": "^3.0.0"
1424 | }
1425 | },
1426 | "supports-color": {
1427 | "version": "5.4.0",
1428 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
1429 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
1430 | "dev": true,
1431 | "requires": {
1432 | "has-flag": "^3.0.0"
1433 | }
1434 | },
1435 | "type-is": {
1436 | "version": "1.6.15",
1437 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz",
1438 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=",
1439 | "requires": {
1440 | "media-typer": "0.3.0",
1441 | "mime-types": "~2.1.15"
1442 | }
1443 | },
1444 | "typedarray": {
1445 | "version": "0.0.6",
1446 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1447 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
1448 | },
1449 | "unpipe": {
1450 | "version": "1.0.0",
1451 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1452 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
1453 | },
1454 | "util": {
1455 | "version": "0.10.3",
1456 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
1457 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
1458 | "requires": {
1459 | "inherits": "2.0.1"
1460 | },
1461 | "dependencies": {
1462 | "inherits": {
1463 | "version": "2.0.1",
1464 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
1465 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
1466 | }
1467 | }
1468 | },
1469 | "util-deprecate": {
1470 | "version": "1.0.2",
1471 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1472 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
1473 | },
1474 | "utils-merge": {
1475 | "version": "1.0.1",
1476 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1477 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
1478 | },
1479 | "validate-npm-package-license": {
1480 | "version": "3.0.4",
1481 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
1482 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
1483 | "dev": true,
1484 | "requires": {
1485 | "spdx-correct": "^3.0.0",
1486 | "spdx-expression-parse": "^3.0.0"
1487 | }
1488 | },
1489 | "vary": {
1490 | "version": "1.1.2",
1491 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1492 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
1493 | },
1494 | "wrappy": {
1495 | "version": "1.0.2",
1496 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1497 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1498 | "dev": true
1499 | },
1500 | "xtend": {
1501 | "version": "4.0.1",
1502 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
1503 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
1504 | }
1505 | }
1506 | }
1507 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xmysql",
3 | "version": "0.6.0",
4 | "description": "One command to generate REST APIs for any MySql database",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "./node_modules/.bin/mocha tests/*.js --exit"
8 | },
9 | "keywords": [
10 | "mysql-rest-api-generator",
11 | "node-mysql-rest-api",
12 | "rest",
13 | "restful",
14 | "rest-apis"
15 | ],
16 | "engines": {
17 | "node": ">= 7.6.0"
18 | },
19 | "bin": {
20 | "xmysql": "bin/index.js"
21 | },
22 | "repository": "o1lab/xmysql",
23 | "homepage": "https://github.com/nocodb/nocodb",
24 | "author": "o1lab",
25 | "license": "MIT",
26 | "dependencies": {
27 | "assert": "^1.4.1",
28 | "body-parser": "^1.18.2",
29 | "cluster": "^0.7.7",
30 | "colors": "^1.1.2",
31 | "commander": "^2.11.0",
32 | "cors": "^2.8.4",
33 | "express": "^4.16.1",
34 | "morgan": "^1.9.0",
35 | "multer": "^1.3.0",
36 | "mysql": "^2.18.1"
37 | },
38 | "devDependencies": {
39 | "eslint-config-airbnb-base": "^13.1.0",
40 | "eslint-config-prettier": "^4.1.0",
41 | "eslint-plugin-import": "^2.16.0",
42 | "eslint-plugin-prettier": "^3.0.1",
43 | "mocha": "^5.2.0",
44 | "should": "^13.1.2",
45 | "sleep": "^6.1.0",
46 | "supertest": "^3.0.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------