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.
Files changed (63) hide show
  1. package/Readme.md +16 -55
  2. package/lib/application/index.js +174 -63
  3. package/lib/body/extract.js +89 -0
  4. package/lib/constants/Bounds.js +15 -0
  5. package/lib/constants/Clauses.js +13 -0
  6. package/lib/constants/ResponseFormats.js +2 -2
  7. package/lib/controllers/methods/index.js +7 -0
  8. package/lib/controllers/mixins/index.js +36 -36
  9. package/lib/database/connection.js +6 -0
  10. package/lib/database/migration.js +14 -4
  11. package/lib/facades/methods/index.js +16 -16
  12. package/lib/facades/mixins/index.js +67 -13
  13. package/lib/factories/responses/rest.js +25 -13
  14. package/lib/http/codes/descriptions.js +82 -0
  15. package/lib/http/codes/index.js +70 -145
  16. package/lib/http/codes/symbols.js +82 -0
  17. package/lib/http/{request.js → request/index.js} +53 -75
  18. package/lib/http/request/utils.js +27 -0
  19. package/lib/http/response/headers.js +138 -0
  20. package/lib/http/response/index.js +248 -0
  21. package/lib/http/response/utils.js +38 -0
  22. package/lib/middlewares/SearchParams/index.js +25 -0
  23. package/lib/middlewares/cookies/index.js +44 -0
  24. package/lib/middlewares/etag/index.js +32 -15
  25. package/lib/middlewares/formidable/index.js +30 -25
  26. package/lib/middlewares/ql/sequelize/index.js +13 -4
  27. package/lib/middlewares/ql/sequelize/interpreter/QueryLexer.js +4 -3
  28. package/lib/middlewares/render/index.js +62 -0
  29. package/lib/models/associate.js +25 -1
  30. package/lib/models/define.js +26 -19
  31. package/lib/models/mixins.js +8 -1
  32. package/lib/{queries → query}/traverse.js +118 -77
  33. package/lib/router/handlers.util.js +1 -0
  34. package/lib/router/index.js +194 -99
  35. package/lib/router/markers.js +7 -0
  36. package/lib/router/route.js +5 -0
  37. package/lib/router/routes.util.js +16 -14
  38. package/lib/router/utils.js +7 -0
  39. package/lib/stacks/MarkersStack.js +41 -3
  40. package/lib/stacks/MiddlewaresStack.js +200 -0
  41. package/lib/structures/Enum.js +46 -0
  42. package/lib/structures/Filter.js +156 -0
  43. package/lib/structures/Params.js +55 -0
  44. package/lib/tools/sql.tool.js +7 -0
  45. package/lib/utils/objects.util.js +31 -24
  46. package/lib/utils/sanitizations.util.js +10 -4
  47. package/lib/validators/arguments.js +68 -0
  48. package/lib/validators/dates.js +7 -0
  49. package/lib/validators/numbers.js +7 -0
  50. package/package.json +20 -10
  51. package/lib/database/utils.js +0 -19
  52. package/lib/enums/Enum.js +0 -16
  53. package/lib/http/response.js +0 -1074
  54. package/lib/http/utils.js +0 -254
  55. package/lib/params/Params.js +0 -37
  56. package/lib/policies/Role.js +0 -77
  57. package/lib/policies/RoleExtracting.js +0 -97
  58. package/lib/preprocessors/BodyPreprocessor.js +0 -61
  59. package/lib/queries/Colander.js +0 -107
  60. package/lib/queries/NodesterQueryParams.js +0 -145
  61. package/lib/services/includes.service.js +0 -79
  62. package/lib/services/jwt.service.js +0 -147
  63. package/lib/stacks/MiddlewareStack.js +0 -159
package/lib/http/utils.js DELETED
@@ -1,254 +0,0 @@
1
- /*!
2
- * /nodester
3
- * MIT Licensed
4
- */
5
- 'use strict';
6
-
7
- const contentType = require('content-type');
8
- const mime = require('send').mime;
9
- const etag = require('etag');
10
- const proxyaddr = require('proxy-addr');
11
- const qs = require('qs');
12
- const querystring = require('querystring');
13
-
14
-
15
- exports = module.exports = {
16
- acceptParams: _acceptParams,
17
- normalizeType: _normalizeType,
18
- normalizeTypes: _normalizeTypes,
19
- compileETag: _compileETag,
20
- setCharset: _setCharset
21
- }
22
-
23
-
24
- /**
25
- * Parse accept params `str` returning an
26
- * object with `.value`, `.quality` and `.params`.
27
- * also includes `.originalIndex` for stable sorting
28
- *
29
- * @param {String} str
30
- * @param {Number} index
31
- * @return {Object}
32
- *
33
- * @api private
34
- */
35
- function _acceptParams(str, index) {
36
- const parts = str.split(/ *; */);
37
- let result = { value: parts[0], quality: 1, params: {}, originalIndex: index };
38
-
39
- for (let i = 1; i < parts.length; i++) {
40
- const pms = parts[i].split(/ *= */);
41
- if ('q' === pms[0]) {
42
- result.quality = parseFloat(pms[1]);
43
- } else {
44
- result.params[pms[0]] = pms[1];
45
- }
46
- }
47
-
48
- return result;
49
- }
50
-
51
-
52
- /**
53
- * Create an ETag generator function, generating ETags with
54
- * the given options.
55
- *
56
- * @param {object} options
57
- * @return {function}
58
- *
59
- * @private
60
- */
61
- function _createETagGenerator (options) {
62
- return function generateETag (body, encoding) {
63
- const buf = !Buffer.isBuffer(body)
64
- ? Buffer.from(body, encoding)
65
- : body
66
-
67
- return etag(buf, options)
68
- }
69
- }
70
-
71
-
72
- /**
73
- * Normalize the given `type`, for example "html" becomes "text/html".
74
- *
75
- * @param {String} type
76
- * @return {Object}
77
- *
78
- * @api private
79
- */
80
- function _normalizeType(type){
81
- return ~type.indexOf('/')
82
- ? acceptParams(type)
83
- : { value: mime.lookup(type), params: {} };
84
- };
85
-
86
-
87
- /**
88
- * Normalize `types`, for example "html" becomes "text/html".
89
- *
90
- * @param {Array} types
91
- * @return {Array}
92
- *
93
- * @api private
94
- */
95
- function _normalizeTypes(types){
96
- const ret = [];
97
-
98
- for (let i = 0; i < types.length; ++i) {
99
- ret.push(
100
- _normalizeType(types[i])
101
- );
102
- }
103
-
104
- return ret;
105
- };
106
-
107
-
108
- /**
109
- * Compile "etag" value to function.
110
- *
111
- * @param {Boolean|String|Function} val
112
- * @return {Function}
113
- *
114
- * @alias compileETag
115
- * @api public
116
- */
117
- function _compileETag(val) {
118
- let fn;
119
-
120
- if (typeof val === 'function') {
121
- return val;
122
- }
123
-
124
- switch (val) {
125
- case true:
126
- case 'weak':
127
- fn = _createETagGenerator({ weak: true });
128
- break;
129
- case false:
130
- break;
131
- case 'strong':
132
- fn = _createETagGenerator({ weak: false });
133
- break;
134
- default:
135
- throw new TypeError('unknown value for etag function: ' + val);
136
- }
137
-
138
- return fn;
139
- }
140
-
141
- /**
142
- * Compile "query parser" value to function.
143
- *
144
- * @param {String|Function} val
145
- * @return {Function}
146
- *
147
- * @api private
148
- */
149
-
150
- exports.compileQueryParser = function compileQueryParser(val) {
151
- let fn;
152
-
153
- if (typeof val === 'function') {
154
- return val;
155
- }
156
-
157
- switch (val) {
158
- case true:
159
- case 'simple':
160
- fn = querystring.parse;
161
- break;
162
- case false:
163
- fn = newObject;
164
- break;
165
- case 'extended':
166
- fn = parseExtendedQueryString;
167
- break;
168
- default:
169
- throw new TypeError('unknown value for query parser function: ' + val);
170
- }
171
-
172
- return fn;
173
- }
174
-
175
- /**
176
- * Compile "proxy trust" value to function.
177
- *
178
- * @param {Boolean|String|Number|Array|Function} val
179
- * @return {Function}
180
- *
181
- * @api private
182
- */
183
- exports.compileTrust = function(val) {
184
- if (typeof val === 'function') return val;
185
-
186
- if (val === true) {
187
- // Support plain true/false
188
- return function(){ return true };
189
- }
190
-
191
- if (typeof val === 'number') {
192
- // Support trusting hop count
193
- return function(a, i){ return i < val };
194
- }
195
-
196
- if (typeof val === 'string') {
197
- // Support comma-separated values
198
- val = val.split(',')
199
- .map(function (v) { return v.trim() })
200
- }
201
-
202
- return proxyaddr.compile(val || []);
203
- }
204
-
205
-
206
- /**
207
- * Set the charset in a given Content-Type string.
208
- *
209
- * @param {String} type
210
- * @param {String} charset
211
- * @return {String}
212
- *
213
- * @api private
214
- */
215
- function _setCharset(type, charset) {
216
- if (!type || !charset) {
217
- return type;
218
- }
219
-
220
- // parse type
221
- const parsed = contentType.parse(type);
222
-
223
- // set charset
224
- parsed.parameters.charset = charset;
225
-
226
- // format type
227
- return contentType.format(parsed);
228
- };
229
-
230
-
231
- /**
232
- * Parse an extended query string with qs.
233
- *
234
- * @return {Object}
235
- * @private
236
- */
237
- function parseExtendedQueryString(str) {
238
- return qs.parse(str, {
239
- allowPrototypes: true
240
- });
241
- }
242
-
243
-
244
- /**
245
- * Return new empty object.
246
- *
247
- * @return {Object}
248
- *
249
- * @api private
250
- */
251
- function newObject() {
252
- return {};
253
- }
254
-
@@ -1,37 +0,0 @@
1
-
2
- module.exports = Params;
3
-
4
- /**
5
- * Extracts only values in second argument
6
- * from first argument.
7
- * If such values is missing in first atgument,
8
- * will fallback to the value in second argument.
9
- *
10
- * @param {Object} sourceObj
11
- * @param {Object} defaultValuesList
12
- *
13
- * @return {Object} result
14
- *
15
- * @api public
16
- */
17
- function Params(
18
- sourceObj={},
19
- defaultValuesList={}
20
- ) {
21
- const result = {};
22
-
23
- const keys = Object.keys(defaultValuesList);
24
- for (const key of keys) {
25
-
26
- // If value is not set,
27
- // use default one from 'defaultValuesList':
28
- if (sourceObj[key] === undefined) {
29
- result[key] = defaultValuesList[key];
30
- continue;
31
- }
32
-
33
- result[key] = sourceObj[key];
34
- }
35
-
36
- return result;
37
- }
@@ -1,77 +0,0 @@
1
- // Reponse protocol generator.
2
- const APIResponseFactory = require('nodester/factories/responses/api');
3
-
4
-
5
- module.exports = class RolePolicy {
6
- constructor({
7
- roleName,
8
- jwtFacade,
9
- apiResponseFactory,
10
- }) {
11
- if (!roleName)
12
- throw new Error('"roleName" argument is invalid.');
13
-
14
- if (!jwtFacade)
15
- throw new Error('"jwtFacade" argument is invalid.');
16
-
17
- this.roleName = roleName;
18
- this.jwtFacade = jwtFacade;
19
-
20
- // Init standard API response factory.
21
- const standardAPIResponseFactory = new APIResponseFactory();
22
-
23
- // Set response factory:
24
- this.createOKResponse = apiResponseFactory?.createOKResponse ??
25
- standardAPIResponseFactory.createOKResponse.bind(standardAPIResponseFactory);;
26
- this.createErrorResponse = apiResponseFactory?.createErrorResponse ??
27
- standardAPIResponseFactory.createErrorResponse.bind(standardAPIResponseFactory);;
28
- }
29
-
30
- async dryRun(req, res, next) {
31
- try {
32
- const jwtService = this.jwtFacade.service;
33
-
34
- // Get token either from header, query or body.
35
- const token = jwtService.extractTokenFromRequest(req);
36
-
37
- // Verifys and parses token. On failed validation will throw error.
38
- const parsedToken = await jwtService.verifyAccessToken(token);
39
-
40
- // Check role:
41
- if (parsedToken.role !== this.roleName) {
42
- const err = new Error(`Unauthorized.`);
43
- err.name = 'UnauthorizedError';
44
- err.status = 401;
45
- throw err;
46
- }
47
-
48
- // Everything's good, procceed:
49
- req.token = {
50
- parsed: parsedToken,
51
- initial: token
52
- }
53
-
54
- return Promise.resolve();
55
- }
56
- catch(error) {
57
- // If error is not 401
58
- // or
59
- // error is not "NoToken",
60
- // log it:
61
- if (
62
- ['NoToken', 'UnauthorizedError'].indexOf(error?.name) === -1
63
- ) {
64
- console.error(`${ this.roleName }.policy error:`, error);
65
- }
66
-
67
- const statusCode = error?.status ?? 401;
68
-
69
- const err = new Error(error.message);
70
- err.name = error.name ?? 'UnknownError';
71
- err.details = { message: 'Unauthorized.' }
72
- err.status = statusCode;
73
-
74
- return Promise.reject(err);
75
- }
76
- }
77
- }
@@ -1,97 +0,0 @@
1
- // Format for Role header.
2
- const CLAIMED_ROLE_HEADER_NAME = 'x-claimed-role';
3
- // Reponse protocol generator.
4
- const APIResponseFactory = require('nodester/factories/responses/api');
5
-
6
-
7
- module.exports = class RoleExtractingPolicy {
8
- constructor({
9
- jwtFacade,
10
- apiResponseFactory,
11
- name
12
- }) {
13
-
14
- if (!jwtFacade)
15
- throw new Error('"jwtFacade" argument is invalid.');
16
-
17
- this.jwtFacade = jwtFacade;
18
- this.name = name ?? 'RoleExtracting';
19
-
20
- // Init standard API response factory.
21
- const standardAPIResponseFactory = new APIResponseFactory();
22
-
23
- // Set response factory:
24
- this.createOKResponse = apiResponseFactory?.createOKResponse ??
25
- standardAPIResponseFactory.createOKResponse.bind(standardAPIResponseFactory);;
26
- this.createErrorResponse = apiResponseFactory?.createErrorResponse ??
27
- standardAPIResponseFactory.createErrorResponse.bind(standardAPIResponseFactory);;
28
- }
29
-
30
- async dryRun(req, res, next) {
31
- try {
32
- // Get claimed role.
33
- const claimedRole = req.header(CLAIMED_ROLE_HEADER_NAME);
34
-
35
- // If claimed role is not set or it's a visitor:
36
- if (!claimedRole || claimedRole === 'visitor') {
37
- // Set role as 'visitor'.
38
- req.user = { role: 'visitor' };
39
-
40
- return Promise.resolve();
41
- }
42
-
43
- // Unwrap JWT service.
44
- const jwtService = this.jwtFacade.service;
45
-
46
- try {
47
- // Get access token either from header, query or body.
48
- const token = jwtService.extractTokenFromRequest(req);
49
-
50
- // Verifys and parses token. On failed validation will throw error.
51
- const parsedToken = await jwtService.verifyAccessToken(token);
52
-
53
- // If parsed token's role is not the same as claimed role,
54
- // immediately throw error:
55
- if ( parsedToken?.role !== claimedRole ) {
56
- const err = new Error('Roles do not match');
57
- err.name = 'RolesMismatch';
58
- err.details = { message: 'Unauthorized.' }
59
- err.status = 401;
60
- return Promise.reject(err);
61
- }
62
-
63
- // Everything's good, procceed:
64
- req.token = {
65
- parsed: parsedToken,
66
- initial: token
67
- }
68
-
69
- req.user = { role: parsedToken?.role };
70
- }
71
- catch(accessTokenError) {
72
- // If error is not "NoToken", something bad has happened:
73
- if (accessTokenError?.name !== 'NoToken') {
74
- throw accessTokenError;
75
- }
76
-
77
- // If error is "NoToken":
78
- // Set role as 'visitor'.
79
- req.user = { role: 'visitor' };
80
- }
81
-
82
- return Promise.resolve();
83
- }
84
- catch(error) {
85
- console.error(`${ this.name }.policy error:`, error);
86
-
87
- const statusCode = error?.status ?? 401;
88
-
89
- const err = new Error(error.message);
90
- err.name = error.name ?? 'UnknownError';
91
- err.details = { message: 'Unauthorized.' }
92
- err.status = statusCode;
93
-
94
- return Promise.reject(err);
95
- }
96
- }
97
- }
@@ -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,107 +0,0 @@
1
- /*!
2
- * /nodester
3
- * MIT Licensed
4
- */
5
- 'use strict';
6
-
7
- const CLAUSES = ['limit', 'skip', 'order', 'order_by'];
8
-
9
- const { isModel } = require('../utils/models');
10
-
11
-
12
- module.exports = class Colander {
13
-
14
- /*
15
- *
16
- * @param {Object|Model} optsOrModelDefinition
17
- @ - @param {Model} model
18
- * - @param {Array} fields
19
- * - @param {Array} clauses
20
- * - @param {Object} includes
21
- * - @param {Object} statics
22
- * -- @param {Object} attributes
23
- * -- @param {Object} clauses
24
- *
25
- * @param {Boolean} noLimit
26
- *
27
- */
28
- constructor(optsOrModelDefinition, noLimit=false) {
29
- this._fields = [];
30
- this._clauses = [];
31
- this._includes = {};
32
-
33
- this._statics = {
34
- attributes: {},
35
- clauses: {
36
- limit: 3
37
- }
38
- }
39
- if (noLimit === true) {
40
- delete this._statics.clauses.limit;
41
- }
42
-
43
- // If model:
44
- if (isModel(optsOrModelDefinition)) {
45
- this._fields = Object.keys(optsOrModelDefinition.tableAttributes);
46
- this._clauses = CLAUSES;
47
- }
48
- // If options:
49
- else {
50
- const {
51
- model,
52
- fields,
53
- clauses,
54
- includes,
55
- statics,
56
- } = optsOrModelDefinition;
57
-
58
-
59
- // If fields are array:
60
- if (Array.isArray(fields)) {
61
- this._fields = fields;
62
- }
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
-
70
- if (Array.isArray(clauses)) {
71
- this._clauses = clauses;
72
- }
73
-
74
- if (typeof includes === 'object') {
75
- this._includes = includes;
76
- }
77
-
78
- if (typeof statics === 'object') {
79
- if (typeof statics.attributes === 'object') {
80
- this._statics.attributes = statics.attributes;
81
- }
82
-
83
- if (typeof statics.clauses === 'object') {
84
- this._statics.clauses = statics.clauses;
85
- }
86
- }
87
- }
88
- }
89
-
90
- // Getters:
91
- get fields() {
92
- return this._fields;
93
- }
94
-
95
- get clauses() {
96
- return this._clauses;
97
- }
98
-
99
- get includes() {
100
- return this._includes;
101
- }
102
-
103
- get statics() {
104
- return this._statics;
105
- }
106
- // Getters\
107
- }