nodester 0.0.1 → 0.0.3
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 +33 -39
- package/lib/application/index.js +110 -38
- package/lib/constants/Operations.js +23 -0
- package/lib/controllers/methods/index.js +194 -0
- package/lib/controllers/mixins/index.js +222 -0
- package/lib/database/connection.js +34 -0
- package/lib/database/migration.js +42 -0
- package/lib/database/utils.js +19 -0
- package/lib/enums/Enum.js +16 -0
- package/lib/facades/methods/index.js +173 -0
- package/lib/facades/mixins/index.js +111 -0
- package/lib/factories/errors/CustomError.js +7 -5
- package/lib/factories/responses/html.js +7 -2
- package/lib/factories/responses/rest.js +110 -0
- package/lib/http/codes/index.js +157 -0
- package/lib/{application/http → http}/request.js +6 -30
- package/lib/{application/http → http}/response.js +20 -53
- package/lib/middlewares/etag/index.js +62 -0
- package/lib/middlewares/ql/sequelize/index.js +34 -0
- package/lib/middlewares/ql/sequelize/interpreter/ModelsTree.js +121 -0
- package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +456 -0
- package/lib/models/associate.js +17 -0
- package/lib/models/define.js +56 -14
- package/lib/models/mixins.js +100 -78
- package/lib/params/Params.js +37 -0
- package/lib/queries/Colander.js +84 -0
- package/lib/queries/NodesterQueryParams.js +139 -0
- package/lib/queries/traverse.js +311 -0
- package/lib/router/handlers.util.js +81 -0
- package/lib/router/index.js +441 -0
- package/lib/router/route.js +124 -0
- package/lib/router/routes.util.js +85 -0
- package/lib/router/utils.js +30 -0
- package/lib/stacks/MarkersStack.js +35 -0
- package/lib/{application → stacks}/MiddlewareStack.js +47 -13
- package/lib/utils/path.util.js +3 -1
- package/lib/utils/types.util.js +51 -1
- package/lib/validators/dates.js +25 -0
- package/lib/validators/numbers.js +14 -0
- package/package.json +31 -4
- package/tests/index.test.js +7 -2
- package/tests/nql.test.js +277 -0
- package/docs/App.md +0 -13
- package/docs/Queries.md +0 -61
- package/docs/Readme.md +0 -2
- package/docs/Routing.md +0 -34
- package/examples/goal/index.js +0 -23
- package/examples/rest/index.js +0 -25
- package/examples/rest/node_modules/.package-lock.json +0 -40
- package/examples/rest/package-lock.json +0 -72
- package/examples/rest/package.json +0 -14
- package/lib/constants/ConstantsEnum.js +0 -13
- package/lib/controllers/Controller.js +0 -474
- package/lib/controllers/JWTController.js +0 -240
- package/lib/controllers/ServiceController.js +0 -109
- package/lib/controllers/WebController.js +0 -75
- package/lib/facades/Facade.js +0 -388
- package/lib/facades/FacadeParams.js +0 -11
- package/lib/facades/ServiceFacade.js +0 -17
- package/lib/facades/jwt.facade.js +0 -273
- package/lib/factories/responses/api.js +0 -90
- package/lib/models/DisabledRefreshToken.js +0 -68
- package/lib/models/Extractor.js +0 -320
- package/lib/routers/Default/index.js +0 -143
- package/lib/routers/Default/layer.js +0 -50
- package/lib/routers/Main/index.js +0 -10
- package/lib/routers/Roles/index.js +0 -81
- package/lib/utils/params.util.js +0 -19
- /package/lib/{application/http → http}/utils.js +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Rest response factory.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const ResponseFormats = require('../../constants/ResponseFormats');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
createGenericResponse: _createGenericResponse,
|
|
10
|
+
createOKResponse: _createOKResponse,
|
|
11
|
+
createErrorResponse: _createErrorResponse,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Format for all API responses will be JSON
|
|
17
|
+
* {
|
|
18
|
+
* content: {...}
|
|
19
|
+
* error: {...}
|
|
20
|
+
* }
|
|
21
|
+
* Status code is sent in header.
|
|
22
|
+
*
|
|
23
|
+
* If error is not present, error should be null.
|
|
24
|
+
* If error is present, content can be null (But it's not required).
|
|
25
|
+
*
|
|
26
|
+
* @param {ServerResponse} res
|
|
27
|
+
* @param {Object} options
|
|
28
|
+
*
|
|
29
|
+
* @alias createGenericResponse
|
|
30
|
+
* @api public
|
|
31
|
+
*/
|
|
32
|
+
function _createGenericResponse(
|
|
33
|
+
res,
|
|
34
|
+
options = {
|
|
35
|
+
status: 200,
|
|
36
|
+
content: {},
|
|
37
|
+
error: null,
|
|
38
|
+
format: ResponseFormats.JSON
|
|
39
|
+
}
|
|
40
|
+
) {
|
|
41
|
+
try {
|
|
42
|
+
const data = {
|
|
43
|
+
content: options?.content ?? null,
|
|
44
|
+
error: options?.error ?? null
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
switch(options?.format) {
|
|
48
|
+
case ResponseFormats.JSON:
|
|
49
|
+
return options?.res.status(options?.status).json(data);
|
|
50
|
+
case ResponseFormats.XML:
|
|
51
|
+
// TODO: format data into XML.
|
|
52
|
+
return options?.res.status(options?.status).send(data);
|
|
53
|
+
break;
|
|
54
|
+
default: {
|
|
55
|
+
const err = new TypeError("No format specified.");
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch(error) {
|
|
61
|
+
const err = new Error(`Could not create generic response: ${error.message}`);
|
|
62
|
+
err.name = error?.name;
|
|
63
|
+
err.code = error?.code;
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Sends response with status code 200.
|
|
71
|
+
* Should be called on all successful respones.
|
|
72
|
+
*
|
|
73
|
+
* @param {ServerResponse} res
|
|
74
|
+
* @param <Object> content
|
|
75
|
+
* @param <String> format
|
|
76
|
+
*
|
|
77
|
+
* @alias createOKResponse
|
|
78
|
+
* @api public
|
|
79
|
+
*/
|
|
80
|
+
function _createOKResponse(res, options={}) {
|
|
81
|
+
|
|
82
|
+
return this.createGenericResponse(res, {
|
|
83
|
+
...options,
|
|
84
|
+
status: 200,
|
|
85
|
+
format: options?.format ?? ResponseFormats.JSON
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sends response with provided error code.
|
|
92
|
+
* Should be called on all failed respones.
|
|
93
|
+
*
|
|
94
|
+
* @param {ServerResponse} res
|
|
95
|
+
* @param <Object> error
|
|
96
|
+
* @param <Object> content (optional)
|
|
97
|
+
* @param <Int> status
|
|
98
|
+
* @param <String> format
|
|
99
|
+
*
|
|
100
|
+
* @alias createErrorResponse
|
|
101
|
+
* @api public
|
|
102
|
+
*/
|
|
103
|
+
function _createErrorResponse(res, options) {
|
|
104
|
+
|
|
105
|
+
return this.createGenericResponse(res, {
|
|
106
|
+
...options,
|
|
107
|
+
status: options?.status ?? 500,
|
|
108
|
+
format: options?.format ?? ResponseFormats.JSON
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const symbolsByTag = {
|
|
2
|
+
// Informational:
|
|
3
|
+
CONTINUE: Symbol('100: Continue'),
|
|
4
|
+
SWITCHING_PROTOCOLS: Symbol('101: Switching Protocols'),
|
|
5
|
+
PROCESSING: Symbol('102: Processing'),
|
|
6
|
+
EARLY_HINTS: Symbol('103: Early Hints'),
|
|
7
|
+
|
|
8
|
+
// Success:
|
|
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'),
|
|
19
|
+
|
|
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
|
+
|
|
61
|
+
// Server Errors:
|
|
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 = {
|
|
76
|
+
// Informational:
|
|
77
|
+
100: 'Continue',
|
|
78
|
+
101: 'Switching Protocols',
|
|
79
|
+
102: 'Processing',
|
|
80
|
+
103: 'Early Hints',
|
|
81
|
+
|
|
82
|
+
// Success:
|
|
83
|
+
200: 'OK',
|
|
84
|
+
201: 'Created',
|
|
85
|
+
202: 'Accepted',
|
|
86
|
+
203: 'Non-Authoritative Information',
|
|
87
|
+
204: 'No Content',
|
|
88
|
+
205: 'Reset Content',
|
|
89
|
+
206: 'Partial Content',
|
|
90
|
+
207: 'Multi-Status',
|
|
91
|
+
208: 'Already Reported',
|
|
92
|
+
226: 'IM Used',
|
|
93
|
+
|
|
94
|
+
// Redirections:
|
|
95
|
+
300: 'Multiple Choices',
|
|
96
|
+
301: 'Moved Permanently',
|
|
97
|
+
302: 'Found',
|
|
98
|
+
303: 'See Other',
|
|
99
|
+
304: 'Not Modified',
|
|
100
|
+
305: 'Use Proxy',
|
|
101
|
+
307: 'Temporary Redirect',
|
|
102
|
+
308: 'Permanent Redirect',
|
|
103
|
+
|
|
104
|
+
// Client Errors:
|
|
105
|
+
400: 'Bad Request',
|
|
106
|
+
401: 'Unauthorized',
|
|
107
|
+
402: 'Payment Required',
|
|
108
|
+
403: 'Forbidden',
|
|
109
|
+
404: 'Not Found',
|
|
110
|
+
405: 'Method Not Allowed',
|
|
111
|
+
406: 'Not Acceptable',
|
|
112
|
+
407: 'Proxy Authentication Required',
|
|
113
|
+
408: 'Request Timeout',
|
|
114
|
+
409: 'Conflict',
|
|
115
|
+
410: 'Gone',
|
|
116
|
+
411: 'Length Required',
|
|
117
|
+
412: 'Precondition Failed',
|
|
118
|
+
413: 'Payload Too Large',
|
|
119
|
+
414: 'URI Too Long',
|
|
120
|
+
415: 'Unsupported Media Type',
|
|
121
|
+
416: 'Range Not Satisfiable',
|
|
122
|
+
417: 'Expectation Failed',
|
|
123
|
+
418: 'I\'m a teapot',
|
|
124
|
+
421: 'Misdirected Request',
|
|
125
|
+
422: 'Unprocessable Entity',
|
|
126
|
+
423: 'Locked',
|
|
127
|
+
424: 'Failed Dependency',
|
|
128
|
+
425: 'Too Early',
|
|
129
|
+
426: 'Upgrade Required',
|
|
130
|
+
428: 'Precondition Required',
|
|
131
|
+
429: 'Too Many Requests',
|
|
132
|
+
431: 'Request Header Fields Too Large',
|
|
133
|
+
451: 'Unavailable For Legal Reasons',
|
|
134
|
+
|
|
135
|
+
// Server Errors:
|
|
136
|
+
500: 'Internal Server Error',
|
|
137
|
+
501: 'Not Implemented',
|
|
138
|
+
502: 'Bad Gateway',
|
|
139
|
+
503: 'Service Unavailable',
|
|
140
|
+
504: 'Gateway Timeout',
|
|
141
|
+
505: 'HTTP Version Not Supported',
|
|
142
|
+
506: 'Variant Also Negotiates',
|
|
143
|
+
507: 'Insufficient Storage',
|
|
144
|
+
508: 'Loop Detected',
|
|
145
|
+
510: 'Not Extended',
|
|
146
|
+
511: 'Network Authentication Required'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
descriptions: {
|
|
151
|
+
byCode: descriptionsByCode
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
symbols: {
|
|
155
|
+
byTag: symbolsByTag
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
const accepts = require('accepts');
|
|
8
8
|
const isIP = require('net').isIP;
|
|
9
9
|
const typeis = require('type-is');
|
|
10
|
-
const
|
|
10
|
+
const { IncomingMessage } = require('http');
|
|
11
11
|
const fresh = require('fresh');
|
|
12
12
|
const parseRange = require('range-parser');
|
|
13
13
|
const parse = require('parseurl');
|
|
14
14
|
const proxyaddr = require('proxy-addr');
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
const req =
|
|
17
|
+
const req = IncomingMessage.prototype;
|
|
18
18
|
|
|
19
19
|
module.exports = req;
|
|
20
20
|
|
|
@@ -355,39 +355,15 @@ defineGetter(req, 'path', function path() {
|
|
|
355
355
|
|
|
356
356
|
/**
|
|
357
357
|
* Parse the "Host" header field to a hostname.
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* address, the "X-Forwarded-Host" header field will
|
|
361
|
-
* be trusted.
|
|
358
|
+
* Will return "X-Forwarded-Host" if set,
|
|
359
|
+
* or "Host" as a fallback.
|
|
362
360
|
*
|
|
363
361
|
* @return {String}
|
|
364
362
|
* @public
|
|
365
363
|
*/
|
|
366
364
|
defineGetter(req, 'hostname', function hostname() {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (!host || !trust(this.connection.remoteAddress, 0)) {
|
|
371
|
-
host = this.get('Host');
|
|
372
|
-
}
|
|
373
|
-
else if (host.indexOf(',') !== -1) {
|
|
374
|
-
// Note: X-Forwarded-Host is normally only ever a
|
|
375
|
-
// single value, but this is to be safe.
|
|
376
|
-
host = host.substring(0, host.indexOf(',')).trimRight()
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (!host)
|
|
380
|
-
return;
|
|
381
|
-
|
|
382
|
-
// IPv6 literal support
|
|
383
|
-
const offset = host[0] === '['
|
|
384
|
-
? host.indexOf(']') + 1
|
|
385
|
-
: 0;
|
|
386
|
-
const index = host.indexOf(':', offset);
|
|
387
|
-
|
|
388
|
-
return index !== -1
|
|
389
|
-
? host.substring(0, index)
|
|
390
|
-
: host;
|
|
365
|
+
const host = this.get('X-Forwarded-Host') ?? this.get('Host');
|
|
366
|
+
return host;
|
|
391
367
|
});
|
|
392
368
|
|
|
393
369
|
|
|
@@ -12,7 +12,7 @@ const contentDisposition = require('content-disposition');
|
|
|
12
12
|
const createError = require('http-errors')
|
|
13
13
|
const encodeUrl = require('encodeurl');
|
|
14
14
|
const escapeHtml = require('escape-html');
|
|
15
|
-
const
|
|
15
|
+
const { ServerResponse } = require('http');
|
|
16
16
|
const onFinished = require('on-finished');
|
|
17
17
|
const statuses = require('statuses')
|
|
18
18
|
const sign = require('cookie-signature').sign;
|
|
@@ -28,10 +28,10 @@ const {
|
|
|
28
28
|
const path = require('path');
|
|
29
29
|
const extname = path.extname;
|
|
30
30
|
const resolve = path.resolve;
|
|
31
|
-
const { isAbsolute } = require('
|
|
31
|
+
const { isAbsolute } = require('../utils/path.util');
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
const res =
|
|
34
|
+
const res = ServerResponse.prototype;
|
|
35
35
|
module.exports = res;
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -135,53 +135,26 @@ res.send = function send(body) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
// determine if ETag should be generated
|
|
139
|
-
const etagFn = app.get('etag fn')
|
|
140
|
-
const generateETag = !this.get('ETag') && typeof etagFn === 'function'
|
|
141
|
-
|
|
142
|
-
// populate Content-Length
|
|
143
|
-
let bufferLength = undefined;
|
|
144
|
-
if (chunk !== undefined) {
|
|
145
|
-
if (Buffer.isBuffer(chunk)) {
|
|
146
|
-
// Get length of the Buffer.
|
|
147
|
-
bufferLength = chunk.length
|
|
148
|
-
}
|
|
149
|
-
else if (!generateETag && chunk.length < 1000) {
|
|
150
|
-
// Just calculate length when no ETag + small chunk.
|
|
151
|
-
bufferLength = Buffer.byteLength(chunk, encoding)
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// Convert chunk to Buffer and calculate:
|
|
155
|
-
chunk = Buffer.from(chunk, encoding)
|
|
156
|
-
encoding = undefined;
|
|
157
|
-
bufferLength = chunk.length
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this.set('Content-Length', bufferLength);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Populate ETag
|
|
164
|
-
let etag = undefined;
|
|
165
|
-
if (generateETag && bufferLength !== undefined) {
|
|
166
|
-
if ((etag = etagFn(chunk, encoding))) {
|
|
167
|
-
this.set('ETag', etag);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
138
|
// freshness:
|
|
172
|
-
if (req.fresh) {
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
139
|
+
// if (req.fresh) {
|
|
140
|
+
// this.statusCode = 304;
|
|
141
|
+
// }
|
|
142
|
+
|
|
143
|
+
// Strip irrelevant headers for:
|
|
144
|
+
// - 204 (No Content)
|
|
145
|
+
// - 303 (Not Modified)
|
|
146
|
+
if (
|
|
147
|
+
this.statusCode === 204
|
|
148
|
+
||
|
|
149
|
+
this.statusCode === 304
|
|
150
|
+
) {
|
|
178
151
|
this.removeHeader('Content-Type');
|
|
179
152
|
this.removeHeader('Content-Length');
|
|
180
153
|
this.removeHeader('Transfer-Encoding');
|
|
181
154
|
chunk = '';
|
|
182
155
|
}
|
|
183
156
|
|
|
184
|
-
//
|
|
157
|
+
// Alter headers for 205 (Reset Content):
|
|
185
158
|
if (this.statusCode === 205) {
|
|
186
159
|
this.set('Content-Length', '0')
|
|
187
160
|
this.removeHeader('Transfer-Encoding')
|
|
@@ -189,11 +162,11 @@ res.send = function send(body) {
|
|
|
189
162
|
}
|
|
190
163
|
|
|
191
164
|
if (req.method === 'HEAD') {
|
|
192
|
-
// skip body for HEAD
|
|
165
|
+
// skip body for HEAD.
|
|
193
166
|
this.end();
|
|
194
167
|
}
|
|
195
168
|
else {
|
|
196
|
-
//
|
|
169
|
+
// Respond.
|
|
197
170
|
this.end(chunk, encoding);
|
|
198
171
|
}
|
|
199
172
|
|
|
@@ -214,19 +187,13 @@ res.send = function send(body) {
|
|
|
214
187
|
* @public
|
|
215
188
|
*/
|
|
216
189
|
res.json = function json(obj) {
|
|
217
|
-
const app = this.app;
|
|
218
|
-
|
|
219
|
-
// Settings:
|
|
220
|
-
const escape = app.get('json escape')
|
|
221
|
-
const replacer = app.get('json replacer');
|
|
222
|
-
const spaces = app.get('json spaces');
|
|
223
|
-
const body = stringify(obj, replacer, spaces, escape)
|
|
224
190
|
|
|
225
|
-
// content-type
|
|
191
|
+
// If content-type not set:
|
|
226
192
|
if (!this.get('Content-Type')) {
|
|
227
193
|
this.set('Content-Type', 'application/json');
|
|
228
194
|
}
|
|
229
195
|
|
|
196
|
+
const body = JSON.stringify(obj);
|
|
230
197
|
return this.send(body);
|
|
231
198
|
};
|
|
232
199
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const calculate = require('etag');
|
|
4
|
+
const Stream = require('stream');
|
|
5
|
+
const promisify = require('util').promisify;
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const getFileStats = promisify(fs.stat);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Expose `etag` middleware.
|
|
12
|
+
*
|
|
13
|
+
* Add ETag header field.
|
|
14
|
+
* @param {object} [options] see https://github.com/jshttp/etag#options
|
|
15
|
+
* @param {boolean} [options.weak]
|
|
16
|
+
* @return {Function}
|
|
17
|
+
* @api public
|
|
18
|
+
*/
|
|
19
|
+
module.exports = function etag (options) {
|
|
20
|
+
return async function(req, res, next) {
|
|
21
|
+
await next()
|
|
22
|
+
const entity = await getResponseEntity(req, res)
|
|
23
|
+
setEtag(res, entity, options);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async function getResponseEntity (res) {
|
|
29
|
+
// If body is not defined:
|
|
30
|
+
const { body } = res;
|
|
31
|
+
if (!body || res.get('etag'))
|
|
32
|
+
return;
|
|
33
|
+
|
|
34
|
+
// Status code.
|
|
35
|
+
const status = res.status / 100 | 0;
|
|
36
|
+
|
|
37
|
+
// 2xx
|
|
38
|
+
if (status !== 2)
|
|
39
|
+
return;
|
|
40
|
+
|
|
41
|
+
if (body instanceof Stream) {
|
|
42
|
+
if (!body.path)
|
|
43
|
+
return;
|
|
44
|
+
|
|
45
|
+
const stats = await getFileStats(body.path);
|
|
46
|
+
return stats;
|
|
47
|
+
}
|
|
48
|
+
else if ((typeof body === 'string') || Buffer.isBuffer(body)) {
|
|
49
|
+
return body;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
return JSON.stringify(body);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
function setEtag (res, entity, options) {
|
|
58
|
+
if (!entity)
|
|
59
|
+
return;
|
|
60
|
+
|
|
61
|
+
res.etag = calculate(entity, options);
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const QueryLexer = require('./interpreter/QueryLexer');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module.exports = NodesterQL;
|
|
5
|
+
|
|
6
|
+
async function NodesterQL(req, res, next) {
|
|
7
|
+
// Object, which will be populated with parsed query.
|
|
8
|
+
req.nquery = {};
|
|
9
|
+
|
|
10
|
+
// Unwrap neccessary params.
|
|
11
|
+
const {
|
|
12
|
+
url
|
|
13
|
+
} = req;
|
|
14
|
+
|
|
15
|
+
// If no query, skip:
|
|
16
|
+
if (url.indexOf('?') === -1) {
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Convert to URLSearchParams.
|
|
22
|
+
const queryString = req.url.split('?')[1];
|
|
23
|
+
|
|
24
|
+
const lexer = new QueryLexer(queryString);
|
|
25
|
+
|
|
26
|
+
// Go on!
|
|
27
|
+
req.nquery = lexer.query;
|
|
28
|
+
next();
|
|
29
|
+
}
|
|
30
|
+
catch(error) {
|
|
31
|
+
res.status(422);
|
|
32
|
+
res.json({ error: error.toString() });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const debug = require('debug')('nodester:interpreter:ModelsTree');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ModelsTreeNode {
|
|
5
|
+
constructor(model, parent=null, opts={}) {
|
|
6
|
+
this.model = model;
|
|
7
|
+
this.parent = parent;
|
|
8
|
+
this.activeParam = null;
|
|
9
|
+
this.op = null;
|
|
10
|
+
|
|
11
|
+
// for override:
|
|
12
|
+
this.fields = [];
|
|
13
|
+
this._where = {};
|
|
14
|
+
this.skip = 0;
|
|
15
|
+
this.limit = -1; // No limit
|
|
16
|
+
|
|
17
|
+
this.includes = opts.includes ?? [];
|
|
18
|
+
this.order = opts.order ?? 'asc';
|
|
19
|
+
this.order_by = opts.order_by ?? 'id';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get hasParent() {
|
|
23
|
+
return this.parent !== null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get includesCount() {
|
|
27
|
+
return Object.values(this.includes).length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get hasIncludes() {
|
|
31
|
+
return this.includesCount > 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get where() {
|
|
35
|
+
return this._where;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
resetActiveParam() {
|
|
39
|
+
this.activeParam = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
resetOP() {
|
|
43
|
+
this.op = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
addWhere(condition={}) {
|
|
47
|
+
this._where = {
|
|
48
|
+
...this.where,
|
|
49
|
+
...condition
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
include(modelTreeNode) {
|
|
54
|
+
modelTreeNode.parent = this;
|
|
55
|
+
this.includes.push(modelTreeNode);
|
|
56
|
+
return modelTreeNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toObject() {
|
|
60
|
+
return {
|
|
61
|
+
model: this.model,
|
|
62
|
+
|
|
63
|
+
where: this.where,
|
|
64
|
+
skip: this.skip,
|
|
65
|
+
limit: this.limit,
|
|
66
|
+
order: this.order,
|
|
67
|
+
order_by: this.order_by,
|
|
68
|
+
|
|
69
|
+
fields: this.fields,
|
|
70
|
+
|
|
71
|
+
includes: this.includes.map(i => i.toObject())
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class ModelsTree {
|
|
77
|
+
constructor() {
|
|
78
|
+
this.root = new ModelsTreeNode('root', null);
|
|
79
|
+
this.node = this.root;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
include(model, opts={}) {
|
|
83
|
+
debug('include', model);
|
|
84
|
+
|
|
85
|
+
const node = new ModelsTreeNode(model, this.node, opts);
|
|
86
|
+
this.node.include(node);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
use(model) {
|
|
91
|
+
let foundOne = null;
|
|
92
|
+
// Dirty search:
|
|
93
|
+
for (const include of this.node.includes) {
|
|
94
|
+
if (include.model === model) {
|
|
95
|
+
foundOne = this.node = include;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
debug('use', model, !!foundOne ? '' : '-> failed.');
|
|
101
|
+
|
|
102
|
+
return foundOne;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
up() {
|
|
106
|
+
if (this.node.hasParent) {
|
|
107
|
+
this.node = this.node.parent;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
debug('go up to', this.node.model);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
upToRoot() {
|
|
114
|
+
this.node = this.root;
|
|
115
|
+
|
|
116
|
+
debug('go up to root');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exports.ModelsTreeNode = ModelsTreeNode;
|
|
121
|
+
exports.ModelsTree = ModelsTree;
|