nodester 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/body/extract.js +78 -0
- package/lib/facades/methods/index.js +6 -7
- package/lib/{queries/Colander.js → filters/Filter.js} +25 -23
- package/lib/http/codes/descriptions.js +82 -0
- package/lib/http/codes/index.js +70 -145
- package/lib/http/codes/symbols.js +82 -0
- package/lib/middlewares/ql/sequelize/index.js +2 -2
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +3 -3
- package/lib/models/define.js +7 -4
- package/lib/models/mixins.js +1 -1
- package/lib/{queries → query}/traverse.js +35 -27
- package/lib/router/index.js +2 -2
- package/lib/router/routes.util.js +5 -1
- package/package.json +11 -4
- package/lib/preprocessors/BodyPreprocessor.js +0 -61
- package/lib/queries/NodesterQueryParams.js +0 -145
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const httpCodes = require('nodester/http/codes');
|
|
8
|
+
|
|
9
|
+
const Sanitizations = require('nodester/utils/sanitizations');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
module.exports = extract;
|
|
13
|
+
|
|
14
|
+
function extract(body, filter=null, model) {
|
|
15
|
+
|
|
16
|
+
const sequelize = model.sequelize;
|
|
17
|
+
const modelFields = Object.keys(model.tableAttributes);
|
|
18
|
+
const availableIncludes = Object.keys(model.associations);
|
|
19
|
+
|
|
20
|
+
const bodyEntries = Object.entries(body);
|
|
21
|
+
|
|
22
|
+
const { statics } = filter;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// Result object.
|
|
26
|
+
const newBody = {};
|
|
27
|
+
|
|
28
|
+
for (const [key, value] of bodyEntries) {
|
|
29
|
+
const isInclude = availableIncludes.indexOf(key) > -1;
|
|
30
|
+
const isField = modelFields.indexOf(key) > -1;
|
|
31
|
+
|
|
32
|
+
if ((!isField || filter.fields.indexOf(key) === -1) && !isInclude) {
|
|
33
|
+
const err = new Error(`Field '${ key }' is not available.`);
|
|
34
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isField) {
|
|
39
|
+
const column = model.rawAttributes[key];
|
|
40
|
+
const typeName = column.type.constructor.name;
|
|
41
|
+
// Optional validation.
|
|
42
|
+
const { validation } = column;
|
|
43
|
+
|
|
44
|
+
const sanitizationOptions = {
|
|
45
|
+
fallback: column.defaultValue ?? undefined,
|
|
46
|
+
min: validation?.min ?? undefined,
|
|
47
|
+
max: validation?.max ?? undefined
|
|
48
|
+
}
|
|
49
|
+
const sanitized = Sanitizations[typeName](value, sanitizationOptions);
|
|
50
|
+
|
|
51
|
+
newBody[key] = sanitized;
|
|
52
|
+
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isInclude) {
|
|
57
|
+
const filterIncludes = Object.keys(filter.includes);
|
|
58
|
+
|
|
59
|
+
if (filterIncludes.indexOf(key) === -1) {
|
|
60
|
+
const err = new Error(`Include '${ key }' is not available.`);
|
|
61
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const association = model.associations[key];
|
|
66
|
+
|
|
67
|
+
newBody[key] = extract(value[0], filter.includes[key], association.target);
|
|
68
|
+
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const err = new Error(`Unknown field '${ key }'.`);
|
|
73
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return newBody;
|
|
78
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
1
7
|
const Params = require('nodester/params');
|
|
2
8
|
|
|
3
9
|
const log = require('nodester/loggers/dev');
|
|
@@ -83,21 +89,14 @@ async function _createOne(params) {
|
|
|
83
89
|
try {
|
|
84
90
|
const {
|
|
85
91
|
data,
|
|
86
|
-
includes,
|
|
87
92
|
} = Params(params, {
|
|
88
93
|
data: null,
|
|
89
|
-
includes: null,
|
|
90
94
|
});
|
|
91
95
|
|
|
92
96
|
const instance = await this.model.create({ ...data }, {
|
|
93
97
|
include: this.model.getIncludesList(data)
|
|
94
98
|
});
|
|
95
99
|
|
|
96
|
-
// If includes are set, "find" this record with includes:
|
|
97
|
-
if (!!includes && includes?.length > 0) {
|
|
98
|
-
await instance.reload({ include: includes });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
100
|
const result = {
|
|
102
101
|
[this.modelName.singular]: instance,
|
|
103
102
|
count: 0 + (instance !== null)
|
|
@@ -9,12 +9,12 @@ const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
|
|
|
9
9
|
const { isModel } = require('../utils/models');
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
module.exports = class
|
|
12
|
+
module.exports = class Filter {
|
|
13
13
|
|
|
14
14
|
/*
|
|
15
15
|
*
|
|
16
|
-
* @param {
|
|
17
|
-
|
|
16
|
+
* @param {Model} model
|
|
17
|
+
* @param {Object} opts
|
|
18
18
|
* - @param {Array} fields
|
|
19
19
|
* - @param {Array} clauses
|
|
20
20
|
* - @param {Object} includes
|
|
@@ -25,47 +25,49 @@ module.exports = class Colander {
|
|
|
25
25
|
* @param {Boolean} noLimit
|
|
26
26
|
*
|
|
27
27
|
*/
|
|
28
|
-
constructor(
|
|
28
|
+
constructor(model=null, opts=null) {
|
|
29
|
+
this._model = model;
|
|
30
|
+
|
|
29
31
|
this._fields = [];
|
|
30
32
|
this._clauses = [];
|
|
31
33
|
this._includes = {};
|
|
32
34
|
|
|
33
35
|
this._statics = {
|
|
34
36
|
attributes: {},
|
|
35
|
-
clauses: {
|
|
36
|
-
limit: 3
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (noLimit === true) {
|
|
40
|
-
delete this._statics.clauses.limit;
|
|
37
|
+
clauses: {}
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
//
|
|
44
|
-
if (
|
|
45
|
-
|
|
40
|
+
// noLimit=false
|
|
41
|
+
// if (noLimit === true) {
|
|
42
|
+
// delete this._statics.clauses.limit;
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// If model is present:
|
|
46
|
+
if (isModel(this._model)) {
|
|
47
|
+
this._fields = Object.keys(this._model.tableAttributes);
|
|
46
48
|
this._clauses = CLAUSES;
|
|
49
|
+
|
|
50
|
+
// ...and no options are provided,
|
|
51
|
+
// set default statics:
|
|
52
|
+
if (!opts) {
|
|
53
|
+
this.statics.clauses.limit = 3;
|
|
54
|
+
}
|
|
47
55
|
}
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
|
|
57
|
+
// If options are defined:
|
|
58
|
+
if (!!opts) {
|
|
50
59
|
const {
|
|
51
|
-
model,
|
|
52
60
|
fields,
|
|
53
61
|
clauses,
|
|
54
62
|
includes,
|
|
55
63
|
statics,
|
|
56
|
-
} =
|
|
64
|
+
} = opts;
|
|
57
65
|
|
|
58
66
|
|
|
59
67
|
// If fields are array:
|
|
60
68
|
if (Array.isArray(fields)) {
|
|
61
69
|
this._fields = fields;
|
|
62
70
|
}
|
|
63
|
-
// If fields were not provided,
|
|
64
|
-
// but we have full model definition:
|
|
65
|
-
else if (isModel(model)) {
|
|
66
|
-
this._fields = Object.keys(model.tableAttributes);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
71
|
|
|
70
72
|
if (Array.isArray(clauses)) {
|
|
71
73
|
this._clauses = clauses;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const Enum = require('nodester/enum');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module.exports = new Enum({
|
|
11
|
+
// Informational:
|
|
12
|
+
100: 'Continue',
|
|
13
|
+
101: 'Switching Protocols',
|
|
14
|
+
102: 'Processing',
|
|
15
|
+
103: 'Early Hints',
|
|
16
|
+
|
|
17
|
+
// Success:
|
|
18
|
+
200: 'OK',
|
|
19
|
+
201: 'Created',
|
|
20
|
+
202: 'Accepted',
|
|
21
|
+
203: 'Non-Authoritative Information',
|
|
22
|
+
204: 'No Content',
|
|
23
|
+
205: 'Reset Content',
|
|
24
|
+
206: 'Partial Content',
|
|
25
|
+
207: 'Multi-Status',
|
|
26
|
+
208: 'Already Reported',
|
|
27
|
+
226: 'IM Used',
|
|
28
|
+
|
|
29
|
+
// Redirections:
|
|
30
|
+
300: 'Multiple Choices',
|
|
31
|
+
301: 'Moved Permanently',
|
|
32
|
+
302: 'Found',
|
|
33
|
+
303: 'See Other',
|
|
34
|
+
304: 'Not Modified',
|
|
35
|
+
305: 'Use Proxy',
|
|
36
|
+
307: 'Temporary Redirect',
|
|
37
|
+
308: 'Permanent Redirect',
|
|
38
|
+
|
|
39
|
+
// Client Errors:
|
|
40
|
+
400: 'Bad Request',
|
|
41
|
+
401: 'Unauthorized',
|
|
42
|
+
402: 'Payment Required',
|
|
43
|
+
403: 'Forbidden',
|
|
44
|
+
404: 'Not Found',
|
|
45
|
+
405: 'Method Not Allowed',
|
|
46
|
+
406: 'Not Acceptable',
|
|
47
|
+
407: 'Proxy Authentication Required',
|
|
48
|
+
408: 'Request Timeout',
|
|
49
|
+
409: 'Conflict',
|
|
50
|
+
410: 'Gone',
|
|
51
|
+
411: 'Length Required',
|
|
52
|
+
412: 'Precondition Failed',
|
|
53
|
+
413: 'Payload Too Large',
|
|
54
|
+
414: 'URI Too Long',
|
|
55
|
+
415: 'Unsupported Media Type',
|
|
56
|
+
416: 'Range Not Satisfiable',
|
|
57
|
+
417: 'Expectation Failed',
|
|
58
|
+
418: 'I\'m a teapot',
|
|
59
|
+
421: 'Misdirected Request',
|
|
60
|
+
422: 'Unprocessable Entity',
|
|
61
|
+
423: 'Locked',
|
|
62
|
+
424: 'Failed Dependency',
|
|
63
|
+
425: 'Too Early',
|
|
64
|
+
426: 'Upgrade Required',
|
|
65
|
+
428: 'Precondition Required',
|
|
66
|
+
429: 'Too Many Requests',
|
|
67
|
+
431: 'Request Header Fields Too Large',
|
|
68
|
+
451: 'Unavailable For Legal Reasons',
|
|
69
|
+
|
|
70
|
+
// Server Errors:
|
|
71
|
+
500: 'Internal Server Error',
|
|
72
|
+
501: 'Not Implemented',
|
|
73
|
+
502: 'Bad Gateway',
|
|
74
|
+
503: 'Service Unavailable',
|
|
75
|
+
504: 'Gateway Timeout',
|
|
76
|
+
505: 'HTTP Version Not Supported',
|
|
77
|
+
506: 'Variant Also Negotiates',
|
|
78
|
+
507: 'Insufficient Storage',
|
|
79
|
+
508: 'Loop Detected',
|
|
80
|
+
510: 'Not Extended',
|
|
81
|
+
511: 'Network Authentication Required'
|
|
82
|
+
});
|
package/lib/http/codes/index.js
CHANGED
|
@@ -1,157 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
EARLY_HINTS: Symbol('103: Early Hints'),
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
OK: Symbol('200: OK'),
|
|
10
|
-
CREATED: Symbol('201: Created'),
|
|
11
|
-
ACCEPTED: Symbol('202: Accepted'),
|
|
12
|
-
NON_AUTHORITATIVE_INFORMATION: Symbol('203: Non-Authoritative Information'),
|
|
13
|
-
NO_CONTENT: Symbol('204: No Content'),
|
|
14
|
-
RESET_CONTENT: Symbol('205: Reset Content'),
|
|
15
|
-
PARTIAL_CONTENT: Symbol('206: Partial Content'),
|
|
16
|
-
MULTI_STATUS: Symbol('207: Multi-Status'),
|
|
17
|
-
ALREADY_REPORTED: Symbol('208: Already Reported'),
|
|
18
|
-
IM_USED: Symbol('226: IM Used'),
|
|
7
|
+
const Enum = require('nodester/enum');
|
|
19
8
|
|
|
20
|
-
// Redirections:
|
|
21
|
-
MULTIPLE_CHOICES: Symbol('300: Multiple Choices'),
|
|
22
|
-
MOVED_PERMANENTLY: Symbol('301: Moved Permanently'),
|
|
23
|
-
FOUND: Symbol('302: Found'),
|
|
24
|
-
SEE_OTHER: Symbol('303: See Other'),
|
|
25
|
-
NOT_MODIFIED: Symbol('304: Not Modified'),
|
|
26
|
-
USE_PROXY: Symbol('305: Use Proxy'),
|
|
27
|
-
TEMPORARY_REDIRECT: Symbol('307: Temporary Redirect'),
|
|
28
|
-
PERMANENT_REDIRECT: Symbol('308: Permanent Redirect'),
|
|
29
|
-
|
|
30
|
-
// Client Errors:
|
|
31
|
-
BAD_REQUEST: Symbol('400: Bad Request'),
|
|
32
|
-
UNAUTHORIZED: Symbol('401: Unauthorized'),
|
|
33
|
-
PAYMENT_REQUIRED: Symbol('402: Payment Required'),
|
|
34
|
-
FORBIDDEN: Symbol('403: Forbidden'),
|
|
35
|
-
NOT_FOUND: Symbol('404: Not Found'),
|
|
36
|
-
METHOD_NOT_ALLOWED: Symbol('405: Method Not Allowed'),
|
|
37
|
-
NOT_ACCEPTABLE: Symbol('406: Not Acceptable'),
|
|
38
|
-
PROXY_AUTHENTICATION_REQUIRED: Symbol('407: Proxy Authentication Required'),
|
|
39
|
-
REQUEST_TIMEOUT: Symbol('408: Request Timeout'),
|
|
40
|
-
CONFLICT: Symbol('409: Conflict'),
|
|
41
|
-
GONE: Symbol('410: Gone'),
|
|
42
|
-
LENGTH_REQUIRED: Symbol('411: Length Required'),
|
|
43
|
-
PRECONDITION_FAILED: Symbol('412: Precondition Failed'),
|
|
44
|
-
PAYLOAD_TOO_LARGE: Symbol('413: Payload Too Large'),
|
|
45
|
-
URI_TOO_LONG: Symbol('414: URI Too Long'),
|
|
46
|
-
UNSUPPORTED_MEDIA_TYPE: Symbol('415: Unsupported Media Type'),
|
|
47
|
-
RANGE_NOT_SATISFIABLE: Symbol('416: Range Not Satisfiable'),
|
|
48
|
-
EXPECTATION_FAILED: Symbol('417: Expectation Failed'),
|
|
49
|
-
IM_A_TEAPOT: Symbol("418: I'm a teapot"),
|
|
50
|
-
MISDIRECTED_REQUEST: Symbol('421: Misdirected Request'),
|
|
51
|
-
UNPROCESSABLE_ENTITY: Symbol('422: Unprocessable Entity'),
|
|
52
|
-
LOCKED: Symbol('423: Locked'),
|
|
53
|
-
FAILED_DEPENDENCY: Symbol('424: Failed Dependency'),
|
|
54
|
-
TOO_EARLY: Symbol('425: Too Early'),
|
|
55
|
-
UPGRADE_REQUIRED: Symbol('426: Upgrade Required'),
|
|
56
|
-
PRECONDITION_REQUIRED: Symbol('428: Precondition Required'),
|
|
57
|
-
TOO_MANY_REQUESTS: Symbol('429: Too Many Requests'),
|
|
58
|
-
REQUEST_HEADER_FIELDS_TOO_LARGE: Symbol('431: Request Header Fields Too Large'),
|
|
59
|
-
UNAVAILABLE_FOR_LEGAL_REASONS: Symbol('451: Unavailable For Legal Reasons'),
|
|
60
9
|
|
|
61
|
-
|
|
62
|
-
INTERNAL_SERVER_ERROR: Symbol('500: Internal Server Error'),
|
|
63
|
-
NOT_IMPLEMENTED: Symbol('501: Not Implemented'),
|
|
64
|
-
BAD_GATEWAY: Symbol('502: Bad Gateway'),
|
|
65
|
-
SERVICE_UNAVAILABLE: Symbol('503: Service Unavailable'),
|
|
66
|
-
GATEWAY_TIMEOUT: Symbol('504: Gateway Timeout'),
|
|
67
|
-
HTTP_VERSION_NOT_SUPPORTED: Symbol('505: HTTP Version Not Supported'),
|
|
68
|
-
VARIANT_ALSO_NEGOTIATES: Symbol('506: Variant Also Negotiates'),
|
|
69
|
-
INSUFFICIENT_STORAGE: Symbol('507: Insufficient Storage'),
|
|
70
|
-
LOOP_DETECTED: Symbol('508: Loop Detected'),
|
|
71
|
-
NOT_EXTENDED: Symbol('510: Not Extended'),
|
|
72
|
-
NETWORK_AUTHENTICATION_REQUIRED: Symbol('511: Network Authentication Required')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const descriptionsByCode = {
|
|
10
|
+
module.exports = new Enum({
|
|
76
11
|
// Informational:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
12
|
+
CONTINUE: 100,
|
|
13
|
+
SWITCHING_PROTOCOLS: 101,
|
|
14
|
+
PROCESSING: 102,
|
|
15
|
+
EARLY_HINTS: 103,
|
|
81
16
|
|
|
82
17
|
// Success:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
18
|
+
OK: 200,
|
|
19
|
+
CREATED: 201,
|
|
20
|
+
ACCEPTED: 202,
|
|
21
|
+
NON_AUTHORITATIVE_INFORMATION: 203,
|
|
22
|
+
NO_CONTENT: 204,
|
|
23
|
+
RESET_CONTENT: 205,
|
|
24
|
+
PARTIAL_CONTENT: 206,
|
|
25
|
+
MULTI_STATUS: 207,
|
|
26
|
+
ALREADY_REPORTED: 208,
|
|
27
|
+
IM_USED: 226,
|
|
93
28
|
|
|
94
29
|
// Redirections:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
30
|
+
MULTIPLE_CHOICES: 300,
|
|
31
|
+
MOVED_PERMANENTLY: 301,
|
|
32
|
+
FOUND: 302,
|
|
33
|
+
SEE_OTHER: 303,
|
|
34
|
+
NOT_MODIFIED: 304,
|
|
35
|
+
USE_PROXY: 305,
|
|
36
|
+
TEMPORARY_REDIRECT: 307,
|
|
37
|
+
PERMANENT_REDIRECT: 308,
|
|
103
38
|
|
|
104
39
|
// Client Errors:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
40
|
+
BAD_REQUEST: 400,
|
|
41
|
+
UNAUTHORIZED: 401,
|
|
42
|
+
PAYMENT_REQUIRED: 402,
|
|
43
|
+
FORBIDDEN: 403,
|
|
44
|
+
NOT_FOUND: 404,
|
|
45
|
+
METHOD_NOT_ALLOWED: 405,
|
|
46
|
+
NOT_ACCEPTABLE: 406,
|
|
47
|
+
PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
48
|
+
REQUEST_TIMEOUT: 408,
|
|
49
|
+
CONFLICT: 409,
|
|
50
|
+
GONE: 410,
|
|
51
|
+
LENGTH_REQUIRED: 411,
|
|
52
|
+
PRECONDITION_FAILED: 412,
|
|
53
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
54
|
+
URI_TOO_LONG: 414,
|
|
55
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
56
|
+
RANGE_NOT_SATISFIABLE: 416,
|
|
57
|
+
EXPECTATION_FAILED: 417,
|
|
58
|
+
IM_A_TEAPOT: 418,
|
|
59
|
+
MISDIRECTED_REQUEST: 421,
|
|
60
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
61
|
+
LOCKED: 423,
|
|
62
|
+
FAILED_DEPENDENCY: 424,
|
|
63
|
+
TOO_EARLY: 425,
|
|
64
|
+
UPGRADE_REQUIRED: 426,
|
|
65
|
+
PRECONDITION_REQUIRED: 428,
|
|
66
|
+
TOO_MANY_REQUESTS: 429,
|
|
67
|
+
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
|
68
|
+
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
|
|
134
69
|
|
|
135
70
|
// Server Errors:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
module.exports = {
|
|
150
|
-
descriptions: {
|
|
151
|
-
byCode: descriptionsByCode
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
symbols: {
|
|
155
|
-
byTag: symbolsByTag
|
|
156
|
-
}
|
|
157
|
-
}
|
|
71
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
72
|
+
NOT_IMPLEMENTED: 501,
|
|
73
|
+
BAD_GATEWAY: 502,
|
|
74
|
+
SERVICE_UNAVAILABLE: 503,
|
|
75
|
+
GATEWAY_TIMEOUT: 504,
|
|
76
|
+
HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
77
|
+
VARIANT_ALSO_NEGOTIATES: 506,
|
|
78
|
+
INSUFFICIENT_STORAGE: 507,
|
|
79
|
+
LOOP_DETECTED: 508,
|
|
80
|
+
NOT_EXTENDED: 510,
|
|
81
|
+
NETWORK_AUTHENTICATION_REQUIRED: 511,
|
|
82
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const Enum = require('nodester/enum');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
module.exports = new Enum({
|
|
11
|
+
// Informational:
|
|
12
|
+
CONTINUE: Symbol('100: Continue'),
|
|
13
|
+
SWITCHING_PROTOCOLS: Symbol('101: Switching Protocols'),
|
|
14
|
+
PROCESSING: Symbol('102: Processing'),
|
|
15
|
+
EARLY_HINTS: Symbol('103: Early Hints'),
|
|
16
|
+
|
|
17
|
+
// Success:
|
|
18
|
+
OK: Symbol('200: OK'),
|
|
19
|
+
CREATED: Symbol('201: Created'),
|
|
20
|
+
ACCEPTED: Symbol('202: Accepted'),
|
|
21
|
+
NON_AUTHORITATIVE_INFORMATION: Symbol('203: Non-Authoritative Information'),
|
|
22
|
+
NO_CONTENT: Symbol('204: No Content'),
|
|
23
|
+
RESET_CONTENT: Symbol('205: Reset Content'),
|
|
24
|
+
PARTIAL_CONTENT: Symbol('206: Partial Content'),
|
|
25
|
+
MULTI_STATUS: Symbol('207: Multi-Status'),
|
|
26
|
+
ALREADY_REPORTED: Symbol('208: Already Reported'),
|
|
27
|
+
IM_USED: Symbol('226: IM Used'),
|
|
28
|
+
|
|
29
|
+
// Redirections:
|
|
30
|
+
MULTIPLE_CHOICES: Symbol('300: Multiple Choices'),
|
|
31
|
+
MOVED_PERMANENTLY: Symbol('301: Moved Permanently'),
|
|
32
|
+
FOUND: Symbol('302: Found'),
|
|
33
|
+
SEE_OTHER: Symbol('303: See Other'),
|
|
34
|
+
NOT_MODIFIED: Symbol('304: Not Modified'),
|
|
35
|
+
USE_PROXY: Symbol('305: Use Proxy'),
|
|
36
|
+
TEMPORARY_REDIRECT: Symbol('307: Temporary Redirect'),
|
|
37
|
+
PERMANENT_REDIRECT: Symbol('308: Permanent Redirect'),
|
|
38
|
+
|
|
39
|
+
// Client Errors:
|
|
40
|
+
BAD_REQUEST: Symbol('400: Bad Request'),
|
|
41
|
+
UNAUTHORIZED: Symbol('401: Unauthorized'),
|
|
42
|
+
PAYMENT_REQUIRED: Symbol('402: Payment Required'),
|
|
43
|
+
FORBIDDEN: Symbol('403: Forbidden'),
|
|
44
|
+
NOT_FOUND: Symbol('404: Not Found'),
|
|
45
|
+
METHOD_NOT_ALLOWED: Symbol('405: Method Not Allowed'),
|
|
46
|
+
NOT_ACCEPTABLE: Symbol('406: Not Acceptable'),
|
|
47
|
+
PROXY_AUTHENTICATION_REQUIRED: Symbol('407: Proxy Authentication Required'),
|
|
48
|
+
REQUEST_TIMEOUT: Symbol('408: Request Timeout'),
|
|
49
|
+
CONFLICT: Symbol('409: Conflict'),
|
|
50
|
+
GONE: Symbol('410: Gone'),
|
|
51
|
+
LENGTH_REQUIRED: Symbol('411: Length Required'),
|
|
52
|
+
PRECONDITION_FAILED: Symbol('412: Precondition Failed'),
|
|
53
|
+
PAYLOAD_TOO_LARGE: Symbol('413: Payload Too Large'),
|
|
54
|
+
URI_TOO_LONG: Symbol('414: URI Too Long'),
|
|
55
|
+
UNSUPPORTED_MEDIA_TYPE: Symbol('415: Unsupported Media Type'),
|
|
56
|
+
RANGE_NOT_SATISFIABLE: Symbol('416: Range Not Satisfiable'),
|
|
57
|
+
EXPECTATION_FAILED: Symbol('417: Expectation Failed'),
|
|
58
|
+
IM_A_TEAPOT: Symbol("418: I'm a teapot"),
|
|
59
|
+
MISDIRECTED_REQUEST: Symbol('421: Misdirected Request'),
|
|
60
|
+
UNPROCESSABLE_ENTITY: Symbol('422: Unprocessable Entity'),
|
|
61
|
+
LOCKED: Symbol('423: Locked'),
|
|
62
|
+
FAILED_DEPENDENCY: Symbol('424: Failed Dependency'),
|
|
63
|
+
TOO_EARLY: Symbol('425: Too Early'),
|
|
64
|
+
UPGRADE_REQUIRED: Symbol('426: Upgrade Required'),
|
|
65
|
+
PRECONDITION_REQUIRED: Symbol('428: Precondition Required'),
|
|
66
|
+
TOO_MANY_REQUESTS: Symbol('429: Too Many Requests'),
|
|
67
|
+
REQUEST_HEADER_FIELDS_TOO_LARGE: Symbol('431: Request Header Fields Too Large'),
|
|
68
|
+
UNAVAILABLE_FOR_LEGAL_REASONS: Symbol('451: Unavailable For Legal Reasons'),
|
|
69
|
+
|
|
70
|
+
// Server Errors:
|
|
71
|
+
INTERNAL_SERVER_ERROR: Symbol('500: Internal Server Error'),
|
|
72
|
+
NOT_IMPLEMENTED: Symbol('501: Not Implemented'),
|
|
73
|
+
BAD_GATEWAY: Symbol('502: Bad Gateway'),
|
|
74
|
+
SERVICE_UNAVAILABLE: Symbol('503: Service Unavailable'),
|
|
75
|
+
GATEWAY_TIMEOUT: Symbol('504: Gateway Timeout'),
|
|
76
|
+
HTTP_VERSION_NOT_SUPPORTED: Symbol('505: HTTP Version Not Supported'),
|
|
77
|
+
VARIANT_ALSO_NEGOTIATES: Symbol('506: Variant Also Negotiates'),
|
|
78
|
+
INSUFFICIENT_STORAGE: Symbol('507: Insufficient Storage'),
|
|
79
|
+
LOOP_DETECTED: Symbol('508: Loop Detected'),
|
|
80
|
+
NOT_EXTENDED: Symbol('510: Not Extended'),
|
|
81
|
+
NETWORK_AUTHENTICATION_REQUIRED: Symbol('511: Network Authentication Required')
|
|
82
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const QueryLexer = require('./interpreter/QueryLexer');
|
|
2
|
+
const httpCodes = require('nodester/http/codes');
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
module.exports = NodesterQL;
|
|
@@ -18,7 +19,6 @@ async function NodesterQL(req, res, next) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
try {
|
|
21
|
-
// Convert to URLSearchParams.
|
|
22
22
|
const queryString = req.url.split('?')[1];
|
|
23
23
|
|
|
24
24
|
const lexer = new QueryLexer(queryString);
|
|
@@ -28,7 +28,7 @@ async function NodesterQL(req, res, next) {
|
|
|
28
28
|
next();
|
|
29
29
|
}
|
|
30
30
|
catch(error) {
|
|
31
|
-
res.status(
|
|
31
|
+
res.status(error.status ?? httpCodes.UNPROCESSABLE_ENTITY);
|
|
32
32
|
res.json({ error: error.toString() });
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
const Enum = require('
|
|
8
|
+
const Enum = require('nodester/enum');
|
|
9
9
|
const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
|
|
10
10
|
const util = require('util');
|
|
11
11
|
const debug = require('debug')('nodester:interpreter:QueryLexer');
|
|
@@ -60,7 +60,7 @@ module.exports = class QueryLexer {
|
|
|
60
60
|
const isSubQuery = tree.node.model !== 'root';
|
|
61
61
|
debug({ isSubQuery, startAt });
|
|
62
62
|
|
|
63
|
-
// Token is accumulated char-by-char.
|
|
63
|
+
// Token is String, accumulated char-by-char.
|
|
64
64
|
let token = '';
|
|
65
65
|
// Value of param ('id=10' OR 'fields=id,text').
|
|
66
66
|
let value = [];
|
|
@@ -375,7 +375,7 @@ module.exports = class QueryLexer {
|
|
|
375
375
|
const param = this.parseParamFromToken(token);
|
|
376
376
|
|
|
377
377
|
if (isSubQuery === true && param === 'includes') {
|
|
378
|
-
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.
|
|
378
|
+
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model1' or 'model.model1+model2'.`);
|
|
379
379
|
throw err;
|
|
380
380
|
}
|
|
381
381
|
|
package/lib/models/define.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
1
7
|
// CRUD mixins.
|
|
2
8
|
const { implementsCRUD } = require('./mixins');
|
|
3
9
|
// ORM.
|
|
4
10
|
const { DataTypes } = require('sequelize');
|
|
5
|
-
// NQL.
|
|
6
|
-
const Colander = require('../queries/Colander');
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
module.exports = defineModel;
|
|
@@ -52,7 +56,7 @@ function defineModel(
|
|
|
52
56
|
implementsCRUD(model);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
//
|
|
59
|
+
// Associations:
|
|
56
60
|
model.associate = (models) => {};
|
|
57
61
|
model.getIncludesList = _getIncludesList.bind(model);
|
|
58
62
|
|
|
@@ -61,7 +65,6 @@ function defineModel(
|
|
|
61
65
|
const values = { ...this.get() };
|
|
62
66
|
return values;
|
|
63
67
|
}
|
|
64
|
-
// Instance methods\
|
|
65
68
|
|
|
66
69
|
return model;
|
|
67
70
|
}
|
package/lib/models/mixins.js
CHANGED
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
|
|
11
11
|
// Nodester query:
|
|
12
12
|
const NQLexer = require('../middlewares/ql/sequelize/interpreter/QueryLexer');
|
|
13
|
-
const traverseNQuery = require('
|
|
13
|
+
const traverseNQuery = require('nodester/query/traverse');
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
module.exports = {
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
const { Op } = require('sequelize');
|
|
8
8
|
const NQueryError = require('../factories/errors/NodesterQueryError');
|
|
9
|
+
const httpCodes = require('nodester/http/codes');
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
module.exports = traverse;
|
|
12
13
|
|
|
13
|
-
function traverse(queryNode,
|
|
14
|
+
function traverse(queryNode, filter=null, model) {
|
|
14
15
|
|
|
15
16
|
const sequelize = model.sequelize;
|
|
16
17
|
const fieldsAvailable = Object.keys(model.tableAttributes);
|
|
@@ -33,9 +34,9 @@ function traverse(queryNode, colander=null, model) {
|
|
|
33
34
|
|
|
34
35
|
// Fields:
|
|
35
36
|
//
|
|
36
|
-
// If
|
|
37
|
+
// If Filter is not set,
|
|
37
38
|
// use every available field:
|
|
38
|
-
if (
|
|
39
|
+
if (filter === null) {
|
|
39
40
|
for (let field of fieldsAvailable) {
|
|
40
41
|
// If no query filter or field is requested:
|
|
41
42
|
if (fields.length === 0 || fields.indexOf(field) > -1) {
|
|
@@ -44,20 +45,21 @@ function traverse(queryNode, colander=null, model) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
|
-
//
|
|
48
|
+
// Filter is present:
|
|
48
49
|
else {
|
|
49
50
|
// If no query fields were set,
|
|
50
|
-
// use the ones from
|
|
51
|
+
// use the ones from Filter,
|
|
51
52
|
// If query fields were set,
|
|
52
|
-
// put them through
|
|
53
|
-
for (let field of
|
|
53
|
+
// put them through Filter:
|
|
54
|
+
for (let field of filter.fields) {
|
|
54
55
|
if (fieldsAvailable.indexOf(field) === -1) {
|
|
55
|
-
const err = new TypeError(`
|
|
56
|
+
const err = new TypeError(`Field '${ field }' is not present in model.`);
|
|
57
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
56
58
|
throw err;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// If field is not in available set:
|
|
60
|
-
// if (
|
|
62
|
+
// if (filter.fields.indexOf(field) === -1) {
|
|
61
63
|
// continue;
|
|
62
64
|
// }
|
|
63
65
|
|
|
@@ -72,6 +74,7 @@ function traverse(queryNode, colander=null, model) {
|
|
|
72
74
|
// At least 1 field is mandatory:
|
|
73
75
|
if (newQuery.attributes.length === 0) {
|
|
74
76
|
const err = new TypeError(`No fields were selected.`);
|
|
77
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
75
78
|
throw err;
|
|
76
79
|
}
|
|
77
80
|
// Fields\
|
|
@@ -101,13 +104,14 @@ function traverse(queryNode, colander=null, model) {
|
|
|
101
104
|
if (!isForRootModel) {
|
|
102
105
|
// Check if it's available:
|
|
103
106
|
if (
|
|
104
|
-
!
|
|
107
|
+
!filter
|
|
105
108
|
||
|
|
106
|
-
!
|
|
109
|
+
!filter?.includes[countTarget]
|
|
107
110
|
||
|
|
108
111
|
model.associations[countTarget] === undefined
|
|
109
112
|
) {
|
|
110
|
-
const err = new NQueryError(`Count for ${ countTarget } is not available.`);
|
|
113
|
+
const err = new NQueryError(`Count for '${ countTarget }' is not available.`);
|
|
114
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
111
115
|
throw err;
|
|
112
116
|
}
|
|
113
117
|
|
|
@@ -132,8 +136,8 @@ function traverse(queryNode, colander=null, model) {
|
|
|
132
136
|
const clausesEntries = Object.entries(clauses);
|
|
133
137
|
for (let [clauseName, value] of clausesEntries) {
|
|
134
138
|
// If clause is not available:
|
|
135
|
-
if (
|
|
136
|
-
if (
|
|
139
|
+
if (filter != null) {
|
|
140
|
+
if (filter.clauses.indexOf(clauseName) === -1)
|
|
137
141
|
continue;
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -168,8 +172,9 @@ function traverse(queryNode, colander=null, model) {
|
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
// "statics" override or set any query Clause:
|
|
171
|
-
if (
|
|
172
|
-
const staticClausesEntries = Object.entries(
|
|
175
|
+
if (filter !== null) {
|
|
176
|
+
const staticClausesEntries = Object.entries(filter.statics.clauses);
|
|
177
|
+
|
|
173
178
|
for (let entry of staticClausesEntries) {
|
|
174
179
|
const [clauseName, staticClauseValue] = entry;
|
|
175
180
|
|
|
@@ -235,21 +240,22 @@ function traverse(queryNode, colander=null, model) {
|
|
|
235
240
|
|
|
236
241
|
const includeIndex = leftIncludes.indexOf(includeName);
|
|
237
242
|
if (includeIndex === -1) {
|
|
238
|
-
const err = new TypeError(`No include named ${ includeName }`);
|
|
243
|
+
const err = new TypeError(`No include named '${ includeName }'`);
|
|
244
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
239
245
|
throw err;
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
leftIncludes.splice(includeIndex, 1);
|
|
243
249
|
}
|
|
244
250
|
|
|
245
|
-
_traverseIncludes(includes, model,
|
|
251
|
+
_traverseIncludes(includes, model, filter, newQuery)
|
|
246
252
|
// Includes\
|
|
247
253
|
|
|
248
254
|
|
|
249
255
|
// Where:
|
|
250
256
|
const whereEntries = Object.entries(where);
|
|
251
257
|
for (let [attribute, value] of whereEntries) {
|
|
252
|
-
_parseWhereEntry(attribute, value, newQuery.where,
|
|
258
|
+
_parseWhereEntry(attribute, value, newQuery.where, filter.statics.attributes);
|
|
253
259
|
}
|
|
254
260
|
|
|
255
261
|
// If "where" was not set:
|
|
@@ -262,16 +268,17 @@ function traverse(queryNode, colander=null, model) {
|
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
|
|
265
|
-
function _traverseIncludes(includes, model,
|
|
266
|
-
// If no
|
|
267
|
-
if (
|
|
271
|
+
function _traverseIncludes(includes, model, filter=null, resultQuery) {
|
|
272
|
+
// If no Filter:
|
|
273
|
+
if (filter === null) {
|
|
268
274
|
for (let include of includes) {
|
|
269
275
|
const includeName = include.model;
|
|
270
276
|
const association = model.associations[includeName];
|
|
271
277
|
|
|
272
278
|
// If no such association:
|
|
273
279
|
if (!association) {
|
|
274
|
-
const err = new TypeError(`No include ${ includeName }`);
|
|
280
|
+
const err = new TypeError(`No include '${ includeName }'`);
|
|
281
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
275
282
|
throw err;
|
|
276
283
|
}
|
|
277
284
|
|
|
@@ -282,14 +289,15 @@ function _traverseIncludes(includes, model, colander, resultQuery) {
|
|
|
282
289
|
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
283
290
|
}
|
|
284
291
|
}
|
|
285
|
-
//
|
|
292
|
+
// Filter is present:
|
|
286
293
|
else {
|
|
287
|
-
const
|
|
288
|
-
for (let [includeName,
|
|
294
|
+
const filterIncludeEntries = Object.entries(filter.includes);
|
|
295
|
+
for (let [includeName, includeFilter] of filterIncludeEntries) {
|
|
289
296
|
const association = model.associations[includeName];
|
|
290
297
|
// If no such association:
|
|
291
298
|
if (!association) {
|
|
292
299
|
const err = new TypeError(`No include ${ includeName }`);
|
|
300
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
293
301
|
throw err;
|
|
294
302
|
}
|
|
295
303
|
|
|
@@ -300,7 +308,7 @@ function _traverseIncludes(includes, model, colander, resultQuery) {
|
|
|
300
308
|
|
|
301
309
|
const includeModel = association.target;
|
|
302
310
|
// Build query for this include.
|
|
303
|
-
const associationQuery = traverse(include,
|
|
311
|
+
const associationQuery = traverse(include, filter.includes[includeName], includeModel);
|
|
304
312
|
|
|
305
313
|
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
306
314
|
}
|
package/lib/router/index.js
CHANGED
|
@@ -257,7 +257,7 @@ module.exports = class NodesterRouter {
|
|
|
257
257
|
const handlerType = typeOf(handler);
|
|
258
258
|
|
|
259
259
|
if (handlerType === 'Object' && !this.paths.controllers && !this.paths.providers) {
|
|
260
|
-
const msg = `Please set
|
|
260
|
+
const msg = `Please set 'controllersPath' or 'providersPath' during Router initialization.`;
|
|
261
261
|
const err = new TypeError(msg);
|
|
262
262
|
throw err;
|
|
263
263
|
}
|
|
@@ -396,7 +396,7 @@ module.exports = class NodesterRouter {
|
|
|
396
396
|
extend(key='', fnOrProperty) {
|
|
397
397
|
const keys = Object.keys(this);
|
|
398
398
|
if (keys.indexOf(key) > -1) {
|
|
399
|
-
const err = new TypeError(`Key ${ key } is already present in Router instance
|
|
399
|
+
const err = new TypeError(`Key '${ key }' is already present in Router instance.`);
|
|
400
400
|
throw err;
|
|
401
401
|
}
|
|
402
402
|
|
|
@@ -88,7 +88,11 @@ function _wrapRouteHandler(routeInstance, handler) {
|
|
|
88
88
|
await parsedHandler.before(req.nquery, req, res);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
// If response was not sent,
|
|
92
|
+
// perform action
|
|
93
|
+
if (res.headersSent === false) {
|
|
94
|
+
await providedAction(req, res);
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
97
|
};
|
|
94
98
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodester",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A boilerplate framework for Node.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./lib/application/index.js",
|
|
7
7
|
|
|
8
|
+
"./body/extract": "./lib/body/extract.js",
|
|
9
|
+
|
|
8
10
|
"./constants/ErrorCodes": "./lib/constants/ErrorCodes.js",
|
|
9
11
|
|
|
10
12
|
"./controllers/methods": "./lib/controllers/methods/index.js",
|
|
@@ -22,7 +24,12 @@
|
|
|
22
24
|
"./factories/errors": "./lib/factories/errors/index.js",
|
|
23
25
|
"./factories/responses/rest": "./lib/factories/responses/rest/index.js",
|
|
24
26
|
|
|
27
|
+
"./filter": "./lib/filters/Filter.js",
|
|
28
|
+
|
|
25
29
|
"./http/codes": "./lib/http/codes/index.js",
|
|
30
|
+
"./http/codes/descriptions": "./lib/http/codes/descriptions.js",
|
|
31
|
+
"./http/codes/symbols": "./lib/http/codes/symbols.js",
|
|
32
|
+
|
|
26
33
|
"./loggers/console": "./lib/loggers/console.js",
|
|
27
34
|
"./loggers/dev": "./lib/loggers/dev.js",
|
|
28
35
|
|
|
@@ -34,14 +41,14 @@
|
|
|
34
41
|
"./params": "./lib/params/Params.js",
|
|
35
42
|
|
|
36
43
|
"./ql/sequelize": "./lib/middlewares/ql/sequelize",
|
|
37
|
-
"./
|
|
38
|
-
"./queries/traverse": "./lib/queries/traverse.js",
|
|
44
|
+
"./query/traverse": "./lib/query/traverse.js",
|
|
39
45
|
|
|
40
46
|
"./route": "./lib/router/route.js",
|
|
41
47
|
"./router": "./lib/router/index.js",
|
|
42
48
|
|
|
43
49
|
"./utils/sql": "./lib/utils/sql.util.js",
|
|
44
|
-
"./utils/strings": "./lib/utils/strings.util.js"
|
|
50
|
+
"./utils/strings": "./lib/utils/strings.util.js",
|
|
51
|
+
"./utils/sanitizations": "./lib/utils/sanitizations.util.js"
|
|
45
52
|
},
|
|
46
53
|
"directories": {
|
|
47
54
|
"doc": "docs"
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// Constants.
|
|
2
|
-
const VISITOR = 'visitor';
|
|
3
|
-
|
|
4
|
-
// Custom error.
|
|
5
|
-
const { Err } = require('nodester/factories/errors');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
module.exports = class BodyPreprocessor {
|
|
9
|
-
constructor(
|
|
10
|
-
availableParamsForRoles,
|
|
11
|
-
staticParamsForRoles,
|
|
12
|
-
customProcessFunction
|
|
13
|
-
) {
|
|
14
|
-
this.availableParamsForRoles = availableParamsForRoles ?? {};
|
|
15
|
-
this.staticParamsForRoles = staticParamsForRoles ?? {};
|
|
16
|
-
|
|
17
|
-
this.customProcessFunction = customProcessFunction ? customProcessFunction : ()=>{};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async extract(
|
|
21
|
-
req,
|
|
22
|
-
role
|
|
23
|
-
) {
|
|
24
|
-
try {
|
|
25
|
-
const requestBody = req.body;
|
|
26
|
-
|
|
27
|
-
if (!requestBody || typeof requestBody !== 'object') {
|
|
28
|
-
const err = new Err();
|
|
29
|
-
err.name = 'ValidationError';
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Get role or set "visitor"
|
|
34
|
-
const _role = typeof role === 'string' && role.length > 1 ? role : VISITOR;
|
|
35
|
-
|
|
36
|
-
const resultBody = {};
|
|
37
|
-
|
|
38
|
-
const params = this.availableParamsForRoles[_role] ?? [];
|
|
39
|
-
const staticValues = this.staticParamsForRoles[_role] ?? {};
|
|
40
|
-
|
|
41
|
-
params.forEach((param) => {
|
|
42
|
-
// If such param is set in body:
|
|
43
|
-
if (!!requestBody[param]) {
|
|
44
|
-
resultBody[param] = staticValues[param] ?? requestBody[param];
|
|
45
|
-
}
|
|
46
|
-
// If such param is not set, but we have a "static" for it:
|
|
47
|
-
else if (!requestBody[param] && !!staticValues[param]) {
|
|
48
|
-
resultBody[param] = staticValues[param];
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Make further preprocessing using customly defined function.
|
|
53
|
-
await this.customProcessFunction.call(this, req, role, resultBody);
|
|
54
|
-
|
|
55
|
-
return Promise.resolve(resultBody);
|
|
56
|
-
}
|
|
57
|
-
catch(error) {
|
|
58
|
-
return Promise.reject(error);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* /nodester
|
|
3
|
-
* MIT Licensed
|
|
4
|
-
*/
|
|
5
|
-
'use strict';
|
|
6
|
-
|
|
7
|
-
// Dictionary of unsafe characters:
|
|
8
|
-
const NOT_ALLOWED = [
|
|
9
|
-
'{',
|
|
10
|
-
'}',
|
|
11
|
-
// `\`,
|
|
12
|
-
'^',
|
|
13
|
-
'~',
|
|
14
|
-
'[',
|
|
15
|
-
']',
|
|
16
|
-
'`'
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const util = require('util');
|
|
20
|
-
|
|
21
|
-
/*
|
|
22
|
-
* NodesterQueryParams is a ready-to-use replacement for URLSearchParams.
|
|
23
|
-
* The only difference is that NodesterQueryParams
|
|
24
|
-
* respects nested "&" during parsing.
|
|
25
|
-
*/
|
|
26
|
-
module.exports = class NodesterQueryParams {
|
|
27
|
-
constructor(queryString='') {
|
|
28
|
-
// Type validateion:
|
|
29
|
-
if (typeof queryString !== 'string') {
|
|
30
|
-
const err = new TypeError(`'query' must be a String.`);
|
|
31
|
-
throw err;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// You never know if it's encoded or not.
|
|
35
|
-
const decoded = decodeURI(queryString);
|
|
36
|
-
|
|
37
|
-
// Indicates, how deep the char is inside different ().
|
|
38
|
-
let deep = 0;
|
|
39
|
-
|
|
40
|
-
const paramLevels = {};
|
|
41
|
-
|
|
42
|
-
// Current query parameter.
|
|
43
|
-
let param = '';
|
|
44
|
-
|
|
45
|
-
// Current query token.
|
|
46
|
-
let token = '';
|
|
47
|
-
|
|
48
|
-
this._map = new Map();
|
|
49
|
-
|
|
50
|
-
for (let i=0; i < decoded.length; i++) {
|
|
51
|
-
const char = decoded[i];
|
|
52
|
-
|
|
53
|
-
// Validate char:
|
|
54
|
-
if (NOT_ALLOWED.indexOf(char) > -1) {
|
|
55
|
-
const err = new TypeError(`Invalid query token at ${ i }: '${ char }'`);
|
|
56
|
-
throw err;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (char === '(') {
|
|
60
|
-
// Error If there is nothing behind:
|
|
61
|
-
if (param.length === 0) {
|
|
62
|
-
const err = new TypeError(`Invalid query token at ${ i }: '${ char }'`);
|
|
63
|
-
throw err;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// If not special token, go deeper:
|
|
67
|
-
if (['and', 'or', 'xor', 'not', '!', '|', 'like'].indexOf(token) === -1) {
|
|
68
|
-
this.append(param, token);
|
|
69
|
-
deep++;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// will set ( in token later.
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (char === ')') {
|
|
76
|
-
// If sub-level:
|
|
77
|
-
if (deep > 0) {
|
|
78
|
-
deep--
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// & can mean the end of key=value pair:
|
|
83
|
-
if (char === '&') {
|
|
84
|
-
// If top-level:
|
|
85
|
-
if (deep === 0) {
|
|
86
|
-
this.append(param, token);
|
|
87
|
-
param = '';
|
|
88
|
-
token = '';
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// If sub-level do nothing.
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// = can mean the end of param name:
|
|
96
|
-
if (char === '=') {
|
|
97
|
-
// If top-level:
|
|
98
|
-
if (deep === 0) {
|
|
99
|
-
param = token;
|
|
100
|
-
token = '';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Continue building token:
|
|
105
|
-
if (char !== '=' || deep > 0 ) {
|
|
106
|
-
token += char;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// If last char:
|
|
110
|
-
if (i === decoded.length-1) {
|
|
111
|
-
// Validate:
|
|
112
|
-
if (deep > 0) {
|
|
113
|
-
const err = new TypeError(`Missing ')' at ${ i }`);
|
|
114
|
-
throw err;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
this.append(param, token);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
append(...args) {
|
|
123
|
-
return this._map.set(...args);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
get(...args) {
|
|
127
|
-
return this._map.get(...args);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
delete(...args) {
|
|
131
|
-
return this._map.delete(...args);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
entries(...args) {
|
|
135
|
-
return this._map.entries(...args);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
toString() {
|
|
139
|
-
return this._map.toString();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
[util.inspect.custom](depth, opts) {
|
|
143
|
-
return this._map;
|
|
144
|
-
}
|
|
145
|
-
}
|