nodester 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +16 -55
- package/lib/application/index.js +174 -63
- package/lib/body/extract.js +89 -0
- 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 +16 -16
- package/lib/facades/mixins/index.js +67 -13
- package/lib/factories/responses/rest.js +25 -13
- 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/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 +13 -4
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +4 -3
- package/lib/middlewares/render/index.js +62 -0
- package/lib/models/associate.js +25 -1
- package/lib/models/define.js +26 -19
- package/lib/models/mixins.js +8 -1
- package/lib/{queries → query}/traverse.js +118 -77
- package/lib/router/handlers.util.js +1 -0
- package/lib/router/index.js +194 -99
- package/lib/router/markers.js +7 -0
- package/lib/router/route.js +5 -0
- package/lib/router/routes.util.js +16 -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 +156 -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 +20 -10
- package/lib/database/utils.js +0 -19
- package/lib/enums/Enum.js +0 -16
- 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/preprocessors/BodyPreprocessor.js +0 -61
- package/lib/queries/Colander.js +0 -107
- package/lib/queries/NodesterQueryParams.js +0 -145
- 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,138 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// Constants.
|
|
9
|
+
const CHARSET_REGEX = /;\s*charset\s*=/;
|
|
10
|
+
|
|
11
|
+
// Utils:
|
|
12
|
+
const mime = require('mime');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
setHeader: _setHeader,
|
|
17
|
+
appendHeader: _appendHeader,
|
|
18
|
+
getHeader: _getHeader,
|
|
19
|
+
removeHeader: _removeHeader,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Set header `field` to `value`, or pass
|
|
24
|
+
* an object of header fields.
|
|
25
|
+
*
|
|
26
|
+
* Examples:
|
|
27
|
+
*
|
|
28
|
+
* res.set('Foo', ['bar', 'baz']);
|
|
29
|
+
* res.set('Accept', 'application/json');
|
|
30
|
+
* res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
|
|
31
|
+
*
|
|
32
|
+
* Aliased as `res.header()`.
|
|
33
|
+
*
|
|
34
|
+
* @param {String|Object} keyOrObject
|
|
35
|
+
* @param {String|Array} value
|
|
36
|
+
*
|
|
37
|
+
* @return {ServerResponse} for chaining
|
|
38
|
+
*
|
|
39
|
+
* @alias setHeader
|
|
40
|
+
* @api public
|
|
41
|
+
*/
|
|
42
|
+
function _setHeader(keyOrObject, value) {
|
|
43
|
+
// As a pair:
|
|
44
|
+
if (arguments.length === 2) {
|
|
45
|
+
let _value = Array.isArray(value) ? value.map(String) : String(value);
|
|
46
|
+
|
|
47
|
+
// Add charset to content-type:
|
|
48
|
+
if (keyOrObject.toLowerCase() === 'content-type') {
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(_value)) {
|
|
51
|
+
throw new TypeError('Content-Type cannot be set to an Array');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!CHARSET_REGEX.test(_value)) {
|
|
55
|
+
const charset = mime.charsets.getType(_value.split(';')[0]);
|
|
56
|
+
|
|
57
|
+
if (charset) {
|
|
58
|
+
_value += '; charset=' + charset.toLowerCase();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.setHeader(keyOrObject, _value);
|
|
64
|
+
}
|
|
65
|
+
// As an object
|
|
66
|
+
else if (typeof keyOrObject === 'object') {
|
|
67
|
+
for (let key in keyOrObject) {
|
|
68
|
+
this.setHeader(key, keyOrObject[key]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const err = new TypeError(`'keyOrObject' must be of type String|Object.`);
|
|
73
|
+
Error.captureStackTrace(err, _setHeader);
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return this;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Append additional header `field` with value `val`.
|
|
83
|
+
*
|
|
84
|
+
* Example:
|
|
85
|
+
*
|
|
86
|
+
* res.append.header('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
|
|
87
|
+
* res.append.header('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
|
|
88
|
+
* res.append.header('Warning', '199 Miscellaneous warning');
|
|
89
|
+
*
|
|
90
|
+
* @param {String} field
|
|
91
|
+
* @param {String|Array} value
|
|
92
|
+
* @return {ServerResponse} for chaining
|
|
93
|
+
*
|
|
94
|
+
* @alias appendHeader
|
|
95
|
+
* @api public
|
|
96
|
+
*/
|
|
97
|
+
function _appendHeader(field, value) {
|
|
98
|
+
const prev = this.get(field);
|
|
99
|
+
let _value = value;
|
|
100
|
+
|
|
101
|
+
if (prev) {
|
|
102
|
+
// concat the new and prev values:
|
|
103
|
+
_value = Array.isArray(prev) ? prev.concat(value)
|
|
104
|
+
: Array.isArray(value) ? [prev].concat(value)
|
|
105
|
+
: [prev, value]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return this.setHeader(field, _value);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get value for header `key`.
|
|
114
|
+
*
|
|
115
|
+
* @param {String} key
|
|
116
|
+
*
|
|
117
|
+
* @return {String}
|
|
118
|
+
*
|
|
119
|
+
* @alias getHeader
|
|
120
|
+
* @api public
|
|
121
|
+
*/
|
|
122
|
+
function _getHeader(key) {
|
|
123
|
+
return this.getHeader(key);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Remove value for header `key`.
|
|
128
|
+
*
|
|
129
|
+
* @param {String} key
|
|
130
|
+
*
|
|
131
|
+
* @return {String}
|
|
132
|
+
*
|
|
133
|
+
* @alias removeHeader
|
|
134
|
+
* @api public
|
|
135
|
+
*/
|
|
136
|
+
function _removeHeader(key) {
|
|
137
|
+
return this.removeHeader(key);
|
|
138
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { ServerResponse } = require('http');
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
setHeader,
|
|
12
|
+
appendHeader,
|
|
13
|
+
getHeader,
|
|
14
|
+
removeHeader,
|
|
15
|
+
} = require('./headers');
|
|
16
|
+
|
|
17
|
+
const calculateEtag = require('etag');
|
|
18
|
+
|
|
19
|
+
// Utils:
|
|
20
|
+
const statuses = require('statuses');
|
|
21
|
+
const mime = require('mime');
|
|
22
|
+
const {
|
|
23
|
+
setCharset
|
|
24
|
+
} = require('./utils');
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const response = ServerResponse.prototype;
|
|
28
|
+
|
|
29
|
+
// Mixin custom methods:
|
|
30
|
+
response.status = _status;
|
|
31
|
+
response.sendStatus = _sendStatus;
|
|
32
|
+
|
|
33
|
+
response.send = _send;
|
|
34
|
+
response.json = _json;
|
|
35
|
+
|
|
36
|
+
// Headers:
|
|
37
|
+
response.set = setHeader;
|
|
38
|
+
response.append = appendHeader;
|
|
39
|
+
response.get = getHeader;
|
|
40
|
+
response.remove = removeHeader;
|
|
41
|
+
|
|
42
|
+
// ContentType:
|
|
43
|
+
response.setContentType = _setContentType;
|
|
44
|
+
response.getContentType = _getContentType;
|
|
45
|
+
// Mixins\
|
|
46
|
+
|
|
47
|
+
module.exports = response;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set status `code`.
|
|
51
|
+
*
|
|
52
|
+
* @param {Number} code
|
|
53
|
+
*
|
|
54
|
+
* @return {ServerResponse}
|
|
55
|
+
*
|
|
56
|
+
* @alias status
|
|
57
|
+
* @api public
|
|
58
|
+
*/
|
|
59
|
+
function _status(code) {
|
|
60
|
+
this.statusCode = code;
|
|
61
|
+
return this;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Send given HTTP status code.
|
|
67
|
+
*
|
|
68
|
+
* Sets the response status to `statusCode` and the body of the
|
|
69
|
+
* response to the standard description from node's http.STATUS_CODES
|
|
70
|
+
* or the statusCode number if no description.
|
|
71
|
+
*
|
|
72
|
+
* Examples:
|
|
73
|
+
*
|
|
74
|
+
* res.sendStatus(200);
|
|
75
|
+
*
|
|
76
|
+
* @param {number} statusCode
|
|
77
|
+
*
|
|
78
|
+
* @alias _sendStatus
|
|
79
|
+
* @api public
|
|
80
|
+
*/
|
|
81
|
+
function _sendStatus(statusCode) {
|
|
82
|
+
const body = statuses.message[statusCode] || String(statusCode);
|
|
83
|
+
|
|
84
|
+
this.statusCode = statusCode;
|
|
85
|
+
this.setContentType('txt');
|
|
86
|
+
|
|
87
|
+
return this.send(body);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sends a response.
|
|
92
|
+
*
|
|
93
|
+
* Examples:
|
|
94
|
+
*
|
|
95
|
+
* res.send(Buffer.from('wahoo'));
|
|
96
|
+
* res.send({ some: 'json' });
|
|
97
|
+
* res.send('<p>some html</p>');
|
|
98
|
+
*
|
|
99
|
+
* @param {string|number|boolean|object|Buffer} body
|
|
100
|
+
*
|
|
101
|
+
* @alias send
|
|
102
|
+
* @api public
|
|
103
|
+
*/
|
|
104
|
+
function _send(body) {
|
|
105
|
+
const req = this.req;
|
|
106
|
+
const app = this.app;
|
|
107
|
+
|
|
108
|
+
let chunk = body;
|
|
109
|
+
let encoding;
|
|
110
|
+
let type;
|
|
111
|
+
|
|
112
|
+
switch (typeof chunk) {
|
|
113
|
+
// strings defaulting to html:
|
|
114
|
+
case 'string':
|
|
115
|
+
if (!this.getContentType()) {
|
|
116
|
+
this.setContentType('html');
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case 'boolean':
|
|
120
|
+
case 'number':
|
|
121
|
+
case 'object':
|
|
122
|
+
if (chunk === null) {
|
|
123
|
+
chunk = '';
|
|
124
|
+
}
|
|
125
|
+
else if (Buffer.isBuffer(chunk)) {
|
|
126
|
+
if (!this.getContentType()) {
|
|
127
|
+
this.setContentType('bin');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
return this.json(chunk);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Write strings in utf-8:
|
|
137
|
+
if (typeof chunk === 'string') {
|
|
138
|
+
encoding = 'utf8';
|
|
139
|
+
type = this.getContentType();
|
|
140
|
+
|
|
141
|
+
// reflect this in content-type
|
|
142
|
+
if (typeof type === 'string') {
|
|
143
|
+
this.setContentType( setCharset(type, 'utf-8') );
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Freshness of the REQUEST:
|
|
148
|
+
if (req.fresh) {
|
|
149
|
+
this.statusCode = 304;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Strip irrelevant headers for:
|
|
153
|
+
// - 204 (No Content)
|
|
154
|
+
// - 303 (Not Modified)
|
|
155
|
+
if (
|
|
156
|
+
this.statusCode === 204
|
|
157
|
+
||
|
|
158
|
+
this.statusCode === 304
|
|
159
|
+
) {
|
|
160
|
+
this.remove('Content-Type');
|
|
161
|
+
this.remove('Content-Length');
|
|
162
|
+
this.remove('Transfer-Encoding');
|
|
163
|
+
chunk = '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Alter headers for 205 (Reset Content):
|
|
167
|
+
if (this.statusCode === 205) {
|
|
168
|
+
this.set('Content-Length', '0');
|
|
169
|
+
this.remove('Transfer-Encoding');
|
|
170
|
+
chunk = ''
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (req.method === 'HEAD') {
|
|
174
|
+
// skip body for HEAD.
|
|
175
|
+
this.end();
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Calculate etag:
|
|
179
|
+
const etag = calculateEtag(chunk);
|
|
180
|
+
this.set('etag', etag);
|
|
181
|
+
|
|
182
|
+
// Respond.
|
|
183
|
+
this.end(chunk, encoding);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Send JSON response.
|
|
192
|
+
*
|
|
193
|
+
* @param {string|number|boolean|object} obj
|
|
194
|
+
*
|
|
195
|
+
* @alias json
|
|
196
|
+
* @api public
|
|
197
|
+
*/
|
|
198
|
+
function _json(obj) {
|
|
199
|
+
|
|
200
|
+
// Ensure content-type:
|
|
201
|
+
if (!this.getContentType()) {
|
|
202
|
+
this.setContentType('application/json');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const body = JSON.stringify(obj);
|
|
206
|
+
return this.send(body);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set _Content-Type_ response header with `type` through `mime.getType()`
|
|
211
|
+
* when it does not contain "/", or set the Content-Type to `type` otherwise.
|
|
212
|
+
*
|
|
213
|
+
* Examples:
|
|
214
|
+
*
|
|
215
|
+
* res.type('.html');
|
|
216
|
+
* res.type('html');
|
|
217
|
+
* res.type('json');
|
|
218
|
+
* res.type('application/json');
|
|
219
|
+
* res.type('png');
|
|
220
|
+
*
|
|
221
|
+
* @param {String} type
|
|
222
|
+
*
|
|
223
|
+
* @return {ServerResponse} for chaining
|
|
224
|
+
*
|
|
225
|
+
* @alias setContentType
|
|
226
|
+
* @api public
|
|
227
|
+
*/
|
|
228
|
+
function _setContentType(type) {
|
|
229
|
+
const contentType = type.indexOf('/') === -1 ?
|
|
230
|
+
mime.getType(type)
|
|
231
|
+
:
|
|
232
|
+
type;
|
|
233
|
+
|
|
234
|
+
this.setHeader('Content-Type', contentType);
|
|
235
|
+
return this;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Returns _Content-Type_ header.
|
|
240
|
+
*
|
|
241
|
+
* @return {String} type
|
|
242
|
+
*
|
|
243
|
+
* @alias getContentType
|
|
244
|
+
* @api public
|
|
245
|
+
*/
|
|
246
|
+
function _getContentType() {
|
|
247
|
+
return this.getHeader('Content-Type');
|
|
248
|
+
};
|
|
@@ -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,9 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
1
8
|
const QueryLexer = require('./interpreter/QueryLexer');
|
|
9
|
+
const httpCodes = require('nodester/http/codes');
|
|
2
10
|
|
|
3
11
|
|
|
4
|
-
module.exports =
|
|
12
|
+
module.exports = function initNodesterQL() {
|
|
13
|
+
return nqlHandle;
|
|
14
|
+
};
|
|
5
15
|
|
|
6
|
-
async function
|
|
16
|
+
async function nqlHandle(req, res, next) {
|
|
7
17
|
// Object, which will be populated with parsed query.
|
|
8
18
|
req.nquery = {};
|
|
9
19
|
|
|
@@ -18,7 +28,6 @@ async function NodesterQL(req, res, next) {
|
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
try {
|
|
21
|
-
// Convert to URLSearchParams.
|
|
22
31
|
const queryString = req.url.split('?')[1];
|
|
23
32
|
|
|
24
33
|
const lexer = new QueryLexer(queryString);
|
|
@@ -28,7 +37,7 @@ async function NodesterQL(req, res, next) {
|
|
|
28
37
|
next();
|
|
29
38
|
}
|
|
30
39
|
catch(error) {
|
|
31
|
-
res.status(
|
|
40
|
+
res.status(error.status ?? httpCodes.UNPROCESSABLE_ENTITY);
|
|
32
41
|
res.json({ error: error.toString() });
|
|
33
42
|
}
|
|
34
43
|
}
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* /nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
'use strict';
|
|
6
7
|
|
|
8
|
+
const Enum = require('nodester/enum');
|
|
7
9
|
|
|
8
|
-
const Enum = require('../../../../enums/Enum');
|
|
9
10
|
const { ModelsTree, ModelsTreeNode } = require('./ModelsTree');
|
|
10
11
|
const util = require('util');
|
|
11
12
|
const debug = require('debug')('nodester:interpreter:QueryLexer');
|
|
@@ -60,7 +61,7 @@ module.exports = class QueryLexer {
|
|
|
60
61
|
const isSubQuery = tree.node.model !== 'root';
|
|
61
62
|
debug({ isSubQuery, startAt });
|
|
62
63
|
|
|
63
|
-
// Token is accumulated char-by-char.
|
|
64
|
+
// Token is String, accumulated char-by-char.
|
|
64
65
|
let token = '';
|
|
65
66
|
// Value of param ('id=10' OR 'fields=id,text').
|
|
66
67
|
let value = [];
|
|
@@ -375,7 +376,7 @@ module.exports = class QueryLexer {
|
|
|
375
376
|
const param = this.parseParamFromToken(token);
|
|
376
377
|
|
|
377
378
|
if (isSubQuery === true && param === 'includes') {
|
|
378
|
-
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.
|
|
379
|
+
const err = new TypeError(`'include' is forbidden inside subquery (position ${ i }). Use: 'model.model1' or 'model.model1+model2'.`);
|
|
379
380
|
throw err;
|
|
380
381
|
}
|
|
381
382
|
|