nodester 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +25 -61
- package/lib/application/index.js +185 -63
- package/lib/body/extract.js +15 -4
- package/lib/constants/Bounds.js +15 -0
- package/lib/constants/Clauses.js +13 -0
- package/lib/constants/ResponseFormats.js +2 -2
- package/lib/controllers/methods/index.js +7 -0
- package/lib/controllers/mixins/index.js +36 -36
- package/lib/database/connection.js +6 -0
- package/lib/database/migration.js +14 -4
- package/lib/facades/methods/index.js +10 -9
- package/lib/facades/mixins/index.js +67 -13
- package/lib/factories/responses/rest.js +25 -13
- package/lib/http/{request.js → request/index.js} +53 -75
- package/lib/http/request/utils.js +27 -0
- package/lib/http/response/headers.js +138 -0
- package/lib/http/response/index.js +248 -0
- package/lib/http/response/utils.js +38 -0
- package/lib/middlewares/SearchParams/index.js +25 -0
- package/lib/middlewares/cookies/index.js +44 -0
- package/lib/middlewares/etag/index.js +32 -15
- package/lib/middlewares/formidable/index.js +30 -25
- package/lib/middlewares/ql/sequelize/index.js +11 -2
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +10 -2
- package/lib/middlewares/render/index.js +62 -0
- package/lib/models/associate.js +25 -1
- package/lib/models/define.js +19 -15
- package/lib/models/mixins.js +7 -0
- package/lib/query/traverse.js +97 -63
- package/lib/router/handlers.util.js +1 -0
- package/lib/router/index.js +193 -98
- package/lib/router/markers.js +7 -0
- package/lib/router/route.js +5 -0
- package/lib/router/routes.util.js +12 -14
- package/lib/router/utils.js +7 -0
- package/lib/stacks/MarkersStack.js +41 -3
- package/lib/stacks/MiddlewaresStack.js +200 -0
- package/lib/structures/Enum.js +46 -0
- package/lib/structures/Filter.js +157 -0
- package/lib/structures/Params.js +55 -0
- package/lib/tools/sql.tool.js +7 -0
- package/lib/utils/objects.util.js +31 -24
- package/lib/utils/sanitizations.util.js +10 -4
- package/lib/validators/arguments.js +68 -0
- package/lib/validators/dates.js +7 -0
- package/lib/validators/numbers.js +7 -0
- package/package.json +11 -8
- package/tests/index.test.js +4 -4
- package/tests/nql.test.js +18 -1
- package/lib/database/utils.js +0 -19
- package/lib/enums/Enum.js +0 -16
- package/lib/filters/Filter.js +0 -109
- package/lib/http/response.js +0 -1074
- package/lib/http/utils.js +0 -254
- package/lib/params/Params.js +0 -37
- package/lib/policies/Role.js +0 -77
- package/lib/policies/RoleExtracting.js +0 -97
- package/lib/services/includes.service.js +0 -79
- package/lib/services/jwt.service.js +0 -147
- package/lib/stacks/MiddlewareStack.js +0 -159
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// Utils:
|
|
9
|
+
const contentType = require('content-type');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
setCharset: _setCharset
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set the charset in a given Content-Type string.
|
|
17
|
+
*
|
|
18
|
+
* @param {String} type
|
|
19
|
+
* @param {String} charset
|
|
20
|
+
*
|
|
21
|
+
* @return {String}
|
|
22
|
+
*
|
|
23
|
+
* @api private
|
|
24
|
+
*/
|
|
25
|
+
function _setCharset(type, charset) {
|
|
26
|
+
if (!type || !charset) {
|
|
27
|
+
return type;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// parse type
|
|
31
|
+
const parsed = contentType.parse(type);
|
|
32
|
+
|
|
33
|
+
// set charset
|
|
34
|
+
parsed.parameters.charset = charset;
|
|
35
|
+
|
|
36
|
+
// format type
|
|
37
|
+
return contentType.format(parsed);
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
module.exports = function initSearchParamsMiddleware() {
|
|
10
|
+
return handle;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function handle(req, res, next) {
|
|
14
|
+
// If no query, skip:
|
|
15
|
+
if (req.url.indexOf('?') === -1) {
|
|
16
|
+
return next();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const querystring = req.url.split('?')[1];
|
|
20
|
+
const params = new URLSearchParams(querystring);
|
|
21
|
+
|
|
22
|
+
req.searchParams = params;
|
|
23
|
+
|
|
24
|
+
next();
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const cookie = require('cookie');
|
|
9
|
+
const cookieSignature = require('cookie-signature');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
module.exports = function initCookiesMiddleware(options={}) {
|
|
13
|
+
const context = {
|
|
14
|
+
options
|
|
15
|
+
}
|
|
16
|
+
return cookiesHandle.bind(context);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function cookiesHandle(req, res, next) {
|
|
20
|
+
try {
|
|
21
|
+
if (req.headers.cookie === undefined) {
|
|
22
|
+
req.cookies = {};
|
|
23
|
+
return next();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cookies = cookie.parse(req.headers.cookie);
|
|
27
|
+
req.cookies = cookies;
|
|
28
|
+
|
|
29
|
+
next();
|
|
30
|
+
}
|
|
31
|
+
catch(error) {
|
|
32
|
+
console.error(error);
|
|
33
|
+
|
|
34
|
+
const statusCode = error.status || 406;
|
|
35
|
+
res.status(statusCode);
|
|
36
|
+
res.json({
|
|
37
|
+
error: {
|
|
38
|
+
message: error.message,
|
|
39
|
+
code: statusCode
|
|
40
|
+
},
|
|
41
|
+
status: statusCode
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,38 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
2
7
|
|
|
3
8
|
const calculate = require('etag');
|
|
4
9
|
const Stream = require('stream');
|
|
10
|
+
|
|
11
|
+
// Utils:
|
|
5
12
|
const promisify = require('util').promisify;
|
|
6
13
|
const fs = require('fs');
|
|
7
|
-
|
|
8
14
|
const getFileStats = promisify(fs.stat);
|
|
9
15
|
|
|
16
|
+
|
|
10
17
|
/**
|
|
11
|
-
*
|
|
18
|
+
* Initialize `etag` middleware.
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} options
|
|
21
|
+
* @param {Boolean} options.weak
|
|
12
22
|
*
|
|
13
|
-
* Add ETag header field.
|
|
14
|
-
* @param {object} [options] see https://github.com/jshttp/etag#options
|
|
15
|
-
* @param {boolean} [options.weak]
|
|
16
23
|
* @return {Function}
|
|
24
|
+
*
|
|
17
25
|
* @api public
|
|
18
26
|
*/
|
|
19
|
-
module.exports = function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const entity = await getResponseEntity(req, res)
|
|
23
|
-
setEtag(res, entity, options);
|
|
27
|
+
module.exports = function initETagMiddleware(options) {
|
|
28
|
+
const context = {
|
|
29
|
+
options
|
|
24
30
|
}
|
|
31
|
+
return handle.bind(context);
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/*
|
|
35
|
+
* Add ETag header field.
|
|
36
|
+
*/
|
|
37
|
+
function handle(req, res, next) {
|
|
38
|
+
// console.log('e', req.headers);
|
|
39
|
+
next();
|
|
40
|
+
}
|
|
27
41
|
|
|
28
|
-
async function
|
|
42
|
+
async function _getResponseEntity(res) {
|
|
29
43
|
// If body is not defined:
|
|
30
44
|
const { body } = res;
|
|
31
45
|
if (!body || res.get('etag'))
|
|
32
46
|
return;
|
|
33
47
|
|
|
34
48
|
// Status code.
|
|
35
|
-
const status = res.
|
|
49
|
+
const status = res.statusCode / 100 | 0;
|
|
50
|
+
|
|
51
|
+
console.log('getResponseEntity', status, { tag: res.get('etag') });
|
|
36
52
|
|
|
37
53
|
// 2xx
|
|
38
54
|
if (status !== 2)
|
|
@@ -45,7 +61,7 @@ async function getResponseEntity (res) {
|
|
|
45
61
|
const stats = await getFileStats(body.path);
|
|
46
62
|
return stats;
|
|
47
63
|
}
|
|
48
|
-
else if (
|
|
64
|
+
else if (typeof body === 'string' || Buffer.isBuffer(body)) {
|
|
49
65
|
return body;
|
|
50
66
|
}
|
|
51
67
|
else {
|
|
@@ -53,8 +69,9 @@ async function getResponseEntity (res) {
|
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
|
|
72
|
+
function _setEtag(res, entity, options) {
|
|
73
|
+
console.log('setEtag', typeof res, entity);
|
|
56
74
|
|
|
57
|
-
function setEtag (res, entity, options) {
|
|
58
75
|
if (!entity)
|
|
59
76
|
return;
|
|
60
77
|
|
|
@@ -2,36 +2,41 @@
|
|
|
2
2
|
* /nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
'use strict';
|
|
6
7
|
|
|
7
8
|
const { formidable } = require('formidable');
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
module.exports = function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const form = formidable(formidableOptions);
|
|
14
|
-
const [fields, files] = await form.parse(req);
|
|
15
|
-
|
|
16
|
-
// Add to request:
|
|
17
|
-
req.form = {
|
|
18
|
-
fields,
|
|
19
|
-
files
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
next();
|
|
23
|
-
}
|
|
24
|
-
catch(error) {
|
|
25
|
-
const statusCode = error.status || 406;
|
|
26
|
-
res.status(statusCode);
|
|
27
|
-
res.json({
|
|
28
|
-
error: {
|
|
29
|
-
message: error.message,
|
|
30
|
-
code: statusCode
|
|
31
|
-
},
|
|
32
|
-
status: statusCode
|
|
33
|
-
});
|
|
34
|
-
}
|
|
11
|
+
module.exports = function initFormidableMiddleware(formidableOptions={}) {
|
|
12
|
+
const context = {
|
|
13
|
+
formidableOptions
|
|
35
14
|
}
|
|
15
|
+
return formidableHandle.bind(context);
|
|
36
16
|
};
|
|
37
17
|
|
|
18
|
+
async function formidableHandle(req, res, next) {
|
|
19
|
+
try {
|
|
20
|
+
const form = formidable(this.formidableOptions);
|
|
21
|
+
const [fields, files] = await form.parse(req);
|
|
22
|
+
|
|
23
|
+
// Add to request:
|
|
24
|
+
req.form = {
|
|
25
|
+
fields,
|
|
26
|
+
files
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
next();
|
|
30
|
+
}
|
|
31
|
+
catch(error) {
|
|
32
|
+
const statusCode = error.status || 406;
|
|
33
|
+
res.status(statusCode);
|
|
34
|
+
res.json({
|
|
35
|
+
error: {
|
|
36
|
+
message: error.message,
|
|
37
|
+
code: statusCode
|
|
38
|
+
},
|
|
39
|
+
status: statusCode
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
1
8
|
const QueryLexer = require('./interpreter/QueryLexer');
|
|
2
9
|
const httpCodes = require('nodester/http/codes');
|
|
3
10
|
|
|
4
11
|
|
|
5
|
-
module.exports =
|
|
12
|
+
module.exports = function initNodesterQL() {
|
|
13
|
+
return nqlHandle;
|
|
14
|
+
};
|
|
6
15
|
|
|
7
|
-
async function
|
|
16
|
+
async function nqlHandle(req, res, next) {
|
|
8
17
|
// Object, which will be populated with parsed query.
|
|
9
18
|
req.nquery = {};
|
|
10
19
|
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* /nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
-
'use strict';
|
|
6
5
|
|
|
6
|
+
'use strict';
|
|
7
7
|
|
|
8
8
|
const Enum = require('nodester/enum');
|
|
9
|
+
|
|
9
10
|
const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
|
|
10
11
|
const util = require('util');
|
|
11
12
|
const debug = require('debug')('nodester:interpreter:QueryLexer');
|
|
@@ -221,6 +222,13 @@ module.exports = class QueryLexer {
|
|
|
221
222
|
const model = token;
|
|
222
223
|
tree.use(model) ?? tree.include(model);
|
|
223
224
|
|
|
225
|
+
// Last token (model) was included,
|
|
226
|
+
// now jump to root and proceed to collect next token (model).
|
|
227
|
+
tree.node.resetActiveParam();
|
|
228
|
+
tree.upToRoot();
|
|
229
|
+
|
|
230
|
+
tree.node.activeParam = 'includes';
|
|
231
|
+
|
|
224
232
|
token = '';
|
|
225
233
|
continue;
|
|
226
234
|
}
|
|
@@ -402,7 +410,7 @@ module.exports = class QueryLexer {
|
|
|
402
410
|
const err = MissingCharError(i+1, ')');
|
|
403
411
|
throw err;
|
|
404
412
|
}
|
|
405
|
-
|
|
413
|
+
|
|
406
414
|
this.setNodeParam(tree.node, token, value);
|
|
407
415
|
|
|
408
416
|
// If end of subquery:
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
module.exports = function initRenderMiddleware() {
|
|
10
|
+
return handle;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function handle(req, res, next) {
|
|
15
|
+
const context = { req, res, next };
|
|
16
|
+
res.render = _render.bind(context);
|
|
17
|
+
next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render `view` with the given `options` and optional callback `fn`.
|
|
23
|
+
* When a callback function is given a response will _not_ be made
|
|
24
|
+
* automatically, otherwise a response of _200_ and _text/html_ is given.
|
|
25
|
+
*
|
|
26
|
+
* Options:
|
|
27
|
+
*
|
|
28
|
+
* - `cache` boolean hinting to the engine it should cache
|
|
29
|
+
* - `filename` filename of the view being rendered
|
|
30
|
+
*
|
|
31
|
+
* @alias render
|
|
32
|
+
* @api public
|
|
33
|
+
*/
|
|
34
|
+
function _render(view, options, callback) {
|
|
35
|
+
const app = this.req.app;
|
|
36
|
+
let done = callback;
|
|
37
|
+
let opts = options || {};
|
|
38
|
+
|
|
39
|
+
const req = this.req;
|
|
40
|
+
const res = this.res;
|
|
41
|
+
const next = this.next;
|
|
42
|
+
|
|
43
|
+
// support callback function as second arg
|
|
44
|
+
if (typeof options === 'function') {
|
|
45
|
+
done = options;
|
|
46
|
+
opts = {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// merge res.locals
|
|
50
|
+
opts._locals = res.locals;
|
|
51
|
+
|
|
52
|
+
// default callback to respond
|
|
53
|
+
done = done || function (err, str) {
|
|
54
|
+
if (err)
|
|
55
|
+
return next(err);
|
|
56
|
+
|
|
57
|
+
res.send(str);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// render
|
|
61
|
+
app.render(view, opts, done);
|
|
62
|
+
};
|
package/lib/models/associate.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
1
7
|
|
|
2
|
-
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
associateModels: _associateModels,
|
|
11
|
+
associateModelsSync: _associateModelsSync
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function _associateModels(databaseConnection) {
|
|
3
15
|
try {
|
|
4
16
|
const models = databaseConnection.models;
|
|
5
17
|
|
|
@@ -15,3 +27,15 @@ module.exports = async function associateModels(databaseConnection) {
|
|
|
15
27
|
return Promise.reject(error);
|
|
16
28
|
}
|
|
17
29
|
}
|
|
30
|
+
|
|
31
|
+
function _associateModelsSync(databaseConnection) {
|
|
32
|
+
const models = databaseConnection.models;
|
|
33
|
+
|
|
34
|
+
const modelNames = Object.keys(models);
|
|
35
|
+
|
|
36
|
+
for (let modelName of modelNames) {
|
|
37
|
+
models[modelName].associate(models);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return models;
|
|
41
|
+
}
|
package/lib/models/define.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* /nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
'use strict';
|
|
6
7
|
|
|
7
8
|
// CRUD mixins.
|
|
@@ -18,7 +19,7 @@ module.exports = defineModel;
|
|
|
18
19
|
* @param {Function} definition
|
|
19
20
|
* @param {Object} options
|
|
20
21
|
* - ... Sequilize model options
|
|
21
|
-
* - noCRUD
|
|
22
|
+
* - @param {Boolean} noCRUD
|
|
22
23
|
*/
|
|
23
24
|
function defineModel(
|
|
24
25
|
databaseConnection,
|
|
@@ -30,16 +31,24 @@ function defineModel(
|
|
|
30
31
|
const _options = {
|
|
31
32
|
// Set snake-cased table name.
|
|
32
33
|
// tableName: underscore( pluralize(modelName) ),
|
|
34
|
+
|
|
33
35
|
// Set snake-case.
|
|
34
36
|
underscored: true,
|
|
37
|
+
|
|
35
38
|
// Enable automatic 'created_at' and 'updated_at' fields.
|
|
36
39
|
timestamps: true,
|
|
37
40
|
|
|
38
|
-
// The only way to get snake-cased timestamps
|
|
41
|
+
// The only way to get snake-cased timestamps:
|
|
42
|
+
// (issue: https://github.com/sequelize/sequelize/issues/10857)
|
|
39
43
|
createdAt: 'created_at',
|
|
40
44
|
updatedAt: 'updated_at',
|
|
41
45
|
deletedAt: 'deleted_at',
|
|
42
46
|
|
|
47
|
+
// Configs related to nodester:
|
|
48
|
+
nodester: {
|
|
49
|
+
output: 'underscored'
|
|
50
|
+
},
|
|
51
|
+
|
|
43
52
|
// Add user-defined options (they can override upper ones).
|
|
44
53
|
...options
|
|
45
54
|
};
|
|
@@ -76,11 +85,8 @@ function _getIncludesList(facadeData=null) {
|
|
|
76
85
|
const associations = this.associations;
|
|
77
86
|
const associationEntries = Object.entries(associations);
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
associationName
|
|
81
|
-
associationDefinition
|
|
82
|
-
]) => {
|
|
83
|
-
const a = { association: associationName };
|
|
88
|
+
for (const [ associationName, associationDefinition ] of associationEntries) {
|
|
89
|
+
const formatted = { association: associationName };
|
|
84
90
|
|
|
85
91
|
if (!!facadeData) {
|
|
86
92
|
// If facade data is set, go deeper:
|
|
@@ -88,19 +94,17 @@ function _getIncludesList(facadeData=null) {
|
|
|
88
94
|
if (keys.indexOf(associationName) > 0) {
|
|
89
95
|
const associationModel = associationDefinition.target;
|
|
90
96
|
|
|
91
|
-
const a = { association: associationName };
|
|
92
97
|
if (Object.entries(associationModel.associations).length > 0) {
|
|
93
98
|
const deepData = facadeData[ associationName ];
|
|
94
|
-
|
|
99
|
+
formatted.include = associationModel.getIncludesList(
|
|
100
|
+
Array.isArray(deepData) ? deepData[0] : deepData
|
|
101
|
+
);
|
|
95
102
|
}
|
|
96
|
-
|
|
97
|
-
result.push( a );
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
105
|
+
|
|
106
|
+
result.push( formatted );
|
|
107
|
+
}
|
|
104
108
|
|
|
105
109
|
return result;
|
|
106
110
|
}
|