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,62 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
module.exports = function initRenderMiddleware() {
|
|
10
|
+
return handle;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function handle(req, res, next) {
|
|
15
|
+
const context = { req, res, next };
|
|
16
|
+
res.render = _render.bind(context);
|
|
17
|
+
next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render `view` with the given `options` and optional callback `fn`.
|
|
23
|
+
* When a callback function is given a response will _not_ be made
|
|
24
|
+
* automatically, otherwise a response of _200_ and _text/html_ is given.
|
|
25
|
+
*
|
|
26
|
+
* Options:
|
|
27
|
+
*
|
|
28
|
+
* - `cache` boolean hinting to the engine it should cache
|
|
29
|
+
* - `filename` filename of the view being rendered
|
|
30
|
+
*
|
|
31
|
+
* @alias render
|
|
32
|
+
* @api public
|
|
33
|
+
*/
|
|
34
|
+
function _render(view, options, callback) {
|
|
35
|
+
const app = this.req.app;
|
|
36
|
+
let done = callback;
|
|
37
|
+
let opts = options || {};
|
|
38
|
+
|
|
39
|
+
const req = this.req;
|
|
40
|
+
const res = this.res;
|
|
41
|
+
const next = this.next;
|
|
42
|
+
|
|
43
|
+
// support callback function as second arg
|
|
44
|
+
if (typeof options === 'function') {
|
|
45
|
+
done = options;
|
|
46
|
+
opts = {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// merge res.locals
|
|
50
|
+
opts._locals = res.locals;
|
|
51
|
+
|
|
52
|
+
// default callback to respond
|
|
53
|
+
done = done || function (err, str) {
|
|
54
|
+
if (err)
|
|
55
|
+
return next(err);
|
|
56
|
+
|
|
57
|
+
res.send(str);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// render
|
|
61
|
+
app.render(view, opts, done);
|
|
62
|
+
};
|
package/lib/models/associate.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
1
7
|
|
|
2
|
-
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
associateModels: _associateModels,
|
|
11
|
+
associateModelsSync: _associateModelsSync
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function _associateModels(databaseConnection) {
|
|
3
15
|
try {
|
|
4
16
|
const models = databaseConnection.models;
|
|
5
17
|
|
|
@@ -15,3 +27,15 @@ module.exports = async function associateModels(databaseConnection) {
|
|
|
15
27
|
return Promise.reject(error);
|
|
16
28
|
}
|
|
17
29
|
}
|
|
30
|
+
|
|
31
|
+
function _associateModelsSync(databaseConnection) {
|
|
32
|
+
const models = databaseConnection.models;
|
|
33
|
+
|
|
34
|
+
const modelNames = Object.keys(models);
|
|
35
|
+
|
|
36
|
+
for (let modelName of modelNames) {
|
|
37
|
+
models[modelName].associate(models);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return models;
|
|
41
|
+
}
|
package/lib/models/define.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
1
8
|
// CRUD mixins.
|
|
2
9
|
const { implementsCRUD } = require('./mixins');
|
|
3
10
|
// ORM.
|
|
4
11
|
const { DataTypes } = require('sequelize');
|
|
5
|
-
// NQL.
|
|
6
|
-
const Colander = require('../queries/Colander');
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
module.exports = defineModel;
|
|
@@ -14,7 +19,7 @@ module.exports = defineModel;
|
|
|
14
19
|
* @param {Function} definition
|
|
15
20
|
* @param {Object} options
|
|
16
21
|
* - ... Sequilize model options
|
|
17
|
-
* - noCRUD
|
|
22
|
+
* - @param {Boolean} noCRUD
|
|
18
23
|
*/
|
|
19
24
|
function defineModel(
|
|
20
25
|
databaseConnection,
|
|
@@ -26,16 +31,24 @@ function defineModel(
|
|
|
26
31
|
const _options = {
|
|
27
32
|
// Set snake-cased table name.
|
|
28
33
|
// tableName: underscore( pluralize(modelName) ),
|
|
34
|
+
|
|
29
35
|
// Set snake-case.
|
|
30
36
|
underscored: true,
|
|
37
|
+
|
|
31
38
|
// Enable automatic 'created_at' and 'updated_at' fields.
|
|
32
39
|
timestamps: true,
|
|
33
40
|
|
|
34
|
-
// The only way to get snake-cased timestamps
|
|
41
|
+
// The only way to get snake-cased timestamps:
|
|
42
|
+
// (issue: https://github.com/sequelize/sequelize/issues/10857)
|
|
35
43
|
createdAt: 'created_at',
|
|
36
44
|
updatedAt: 'updated_at',
|
|
37
45
|
deletedAt: 'deleted_at',
|
|
38
46
|
|
|
47
|
+
// Configs related to nodester:
|
|
48
|
+
nodester: {
|
|
49
|
+
output: 'underscored'
|
|
50
|
+
},
|
|
51
|
+
|
|
39
52
|
// Add user-defined options (they can override upper ones).
|
|
40
53
|
...options
|
|
41
54
|
};
|
|
@@ -52,7 +65,7 @@ function defineModel(
|
|
|
52
65
|
implementsCRUD(model);
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
//
|
|
68
|
+
// Associations:
|
|
56
69
|
model.associate = (models) => {};
|
|
57
70
|
model.getIncludesList = _getIncludesList.bind(model);
|
|
58
71
|
|
|
@@ -61,7 +74,6 @@ function defineModel(
|
|
|
61
74
|
const values = { ...this.get() };
|
|
62
75
|
return values;
|
|
63
76
|
}
|
|
64
|
-
// Instance methods\
|
|
65
77
|
|
|
66
78
|
return model;
|
|
67
79
|
}
|
|
@@ -73,11 +85,8 @@ function _getIncludesList(facadeData=null) {
|
|
|
73
85
|
const associations = this.associations;
|
|
74
86
|
const associationEntries = Object.entries(associations);
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
associationName
|
|
78
|
-
associationDefinition
|
|
79
|
-
]) => {
|
|
80
|
-
const a = { association: associationName };
|
|
88
|
+
for (const [ associationName, associationDefinition ] of associationEntries) {
|
|
89
|
+
const formatted = { association: associationName };
|
|
81
90
|
|
|
82
91
|
if (!!facadeData) {
|
|
83
92
|
// If facade data is set, go deeper:
|
|
@@ -85,19 +94,17 @@ function _getIncludesList(facadeData=null) {
|
|
|
85
94
|
if (keys.indexOf(associationName) > 0) {
|
|
86
95
|
const associationModel = associationDefinition.target;
|
|
87
96
|
|
|
88
|
-
const a = { association: associationName };
|
|
89
97
|
if (Object.entries(associationModel.associations).length > 0) {
|
|
90
98
|
const deepData = facadeData[ associationName ];
|
|
91
|
-
|
|
99
|
+
formatted.include = associationModel.getIncludesList(
|
|
100
|
+
Array.isArray(deepData) ? deepData[0] : deepData
|
|
101
|
+
);
|
|
92
102
|
}
|
|
93
|
-
|
|
94
|
-
result.push( a );
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
});
|
|
105
|
+
|
|
106
|
+
result.push( formatted );
|
|
107
|
+
}
|
|
101
108
|
|
|
102
109
|
return result;
|
|
103
110
|
}
|
package/lib/models/mixins.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* /nodester
|
|
3
|
+
* MIT Licensed
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
1
8
|
/*
|
|
2
9
|
* CRUD mixins for any model:
|
|
3
10
|
*/
|
|
@@ -10,7 +17,7 @@ const {
|
|
|
10
17
|
|
|
11
18
|
// Nodester query:
|
|
12
19
|
const NQLexer = require('../middlewares/ql/sequelize/interpreter/QueryLexer');
|
|
13
|
-
const traverseNQuery = require('
|
|
20
|
+
const traverseNQuery = require('nodester/query/traverse');
|
|
14
21
|
|
|
15
22
|
|
|
16
23
|
module.exports = {
|
|
@@ -2,19 +2,40 @@
|
|
|
2
2
|
* /nodester
|
|
3
3
|
* MIT Licensed
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
'use strict';
|
|
6
7
|
|
|
8
|
+
const BOUNDS = require('../constants/Bounds');
|
|
9
|
+
|
|
7
10
|
const { Op } = require('sequelize');
|
|
8
11
|
const NQueryError = require('../factories/errors/NodesterQueryError');
|
|
12
|
+
const httpCodes = require('nodester/http/codes');
|
|
13
|
+
|
|
14
|
+
const { ensure } = require('../validators/arguments');
|
|
9
15
|
|
|
10
16
|
|
|
11
17
|
module.exports = traverse;
|
|
12
18
|
|
|
13
|
-
function traverse(queryNode,
|
|
19
|
+
function traverse(queryNode, filter=null, model=null) {
|
|
20
|
+
const _model = model ?? filter.model;
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
try {
|
|
23
|
+
ensure(queryNode, 'object,required', 'queryNode');
|
|
24
|
+
ensure(filter, 'object,required', 'filter');
|
|
25
|
+
if (!_model) {
|
|
26
|
+
const err = new TypeError(`'model' must be provided either in 'filter.model' or as a third argument.`);
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch(error) {
|
|
31
|
+
Error.captureStackTrace(error, traverse);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rootModelName = _model.options.name;
|
|
36
|
+
const rootModelAssociations = _model.associations;
|
|
37
|
+
const { sequelize } = _model;
|
|
38
|
+
const fieldsAvailable = Object.keys(_model.tableAttributes);
|
|
18
39
|
|
|
19
40
|
const newQuery = {
|
|
20
41
|
attributes: [],
|
|
@@ -33,9 +54,9 @@ function traverse(queryNode, colander=null, model) {
|
|
|
33
54
|
|
|
34
55
|
// Fields:
|
|
35
56
|
//
|
|
36
|
-
// If
|
|
57
|
+
// If Filter is not set,
|
|
37
58
|
// use every available field:
|
|
38
|
-
if (
|
|
59
|
+
if (filter === null) {
|
|
39
60
|
for (let field of fieldsAvailable) {
|
|
40
61
|
// If no query filter or field is requested:
|
|
41
62
|
if (fields.length === 0 || fields.indexOf(field) > -1) {
|
|
@@ -44,20 +65,21 @@ function traverse(queryNode, colander=null, model) {
|
|
|
44
65
|
}
|
|
45
66
|
}
|
|
46
67
|
}
|
|
47
|
-
//
|
|
68
|
+
// Filter is present:
|
|
48
69
|
else {
|
|
49
70
|
// If no query fields were set,
|
|
50
|
-
// use the ones from
|
|
71
|
+
// use the ones from Filter,
|
|
51
72
|
// If query fields were set,
|
|
52
|
-
// put them through
|
|
53
|
-
for (let field of
|
|
73
|
+
// put them through Filter:
|
|
74
|
+
for (let field of filter.fields) {
|
|
54
75
|
if (fieldsAvailable.indexOf(field) === -1) {
|
|
55
|
-
const err = new TypeError(`
|
|
76
|
+
const err = new TypeError(`Field '${ field }' is not present in model.`);
|
|
77
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
56
78
|
throw err;
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
// If field is not in available set:
|
|
60
|
-
// if (
|
|
82
|
+
// if (filter.fields.indexOf(field) === -1) {
|
|
61
83
|
// continue;
|
|
62
84
|
// }
|
|
63
85
|
|
|
@@ -72,6 +94,7 @@ function traverse(queryNode, colander=null, model) {
|
|
|
72
94
|
// At least 1 field is mandatory:
|
|
73
95
|
if (newQuery.attributes.length === 0) {
|
|
74
96
|
const err = new TypeError(`No fields were selected.`);
|
|
97
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
75
98
|
throw err;
|
|
76
99
|
}
|
|
77
100
|
// Fields\
|
|
@@ -84,10 +107,9 @@ function traverse(queryNode, colander=null, model) {
|
|
|
84
107
|
const countParams = fnParams.args;
|
|
85
108
|
|
|
86
109
|
const [ countTarget ] = countParams;
|
|
87
|
-
const RootModelName = model.options.name;
|
|
88
110
|
// Count can be requested for this model,
|
|
89
111
|
// or for any of the available uncludes.
|
|
90
|
-
const isForRootModel = countTarget ===
|
|
112
|
+
const isForRootModel = countTarget === rootModelName.plural.toLowerCase();
|
|
91
113
|
|
|
92
114
|
// Compile request:
|
|
93
115
|
// Example:
|
|
@@ -101,21 +123,22 @@ function traverse(queryNode, colander=null, model) {
|
|
|
101
123
|
if (!isForRootModel) {
|
|
102
124
|
// Check if it's available:
|
|
103
125
|
if (
|
|
104
|
-
!
|
|
126
|
+
!filter
|
|
105
127
|
||
|
|
106
|
-
!
|
|
128
|
+
!filter?.includes[countTarget]
|
|
107
129
|
||
|
|
108
|
-
|
|
130
|
+
rootModelAssociations[countTarget] === undefined
|
|
109
131
|
) {
|
|
110
|
-
const err = new NQueryError(`Count for ${ countTarget } is not available.`);
|
|
132
|
+
const err = new NQueryError(`Count for '${ countTarget }' is not available.`);
|
|
133
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
111
134
|
throw err;
|
|
112
135
|
}
|
|
113
136
|
|
|
114
137
|
const {
|
|
115
138
|
foreignKey,
|
|
116
139
|
sourceKey
|
|
117
|
-
} =
|
|
118
|
-
rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${
|
|
140
|
+
} = rootModelAssociations[countTarget];
|
|
141
|
+
rawSQL += `${ countTarget } where ${ countTarget }.${ foreignKey }=${ rootModelName.singular }.${ sourceKey })`;
|
|
119
142
|
countAttribute = `${ countTarget }_count`;
|
|
120
143
|
}
|
|
121
144
|
|
|
@@ -132,28 +155,32 @@ function traverse(queryNode, colander=null, model) {
|
|
|
132
155
|
const clausesEntries = Object.entries(clauses);
|
|
133
156
|
for (let [clauseName, value] of clausesEntries) {
|
|
134
157
|
// If clause is not available:
|
|
135
|
-
if (
|
|
136
|
-
if (
|
|
158
|
+
if (filter != null) {
|
|
159
|
+
if (filter.clauses.indexOf(clauseName) === -1)
|
|
137
160
|
continue;
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
switch(clauseName) {
|
|
141
|
-
case 'limit':
|
|
164
|
+
case 'limit': {
|
|
165
|
+
const _value = _setValueWithBounds(value, 'number', filter.bounds.clauses.limit);
|
|
166
|
+
|
|
142
167
|
// Do not set if -1:
|
|
143
|
-
if (
|
|
168
|
+
if (_value === -1)
|
|
144
169
|
continue;
|
|
145
170
|
|
|
146
|
-
newQuery.limit =
|
|
171
|
+
newQuery.limit = _value;
|
|
147
172
|
continue;
|
|
173
|
+
}
|
|
174
|
+
case 'skip': {
|
|
175
|
+
const _value = _setValueWithBounds(value, 'number', filter.bounds.clauses.skip);
|
|
148
176
|
|
|
149
|
-
case 'skip':
|
|
150
177
|
// Do not set if 0:
|
|
151
|
-
if (
|
|
178
|
+
if (_value === 0)
|
|
152
179
|
continue;
|
|
153
180
|
|
|
154
|
-
newQuery.offset =
|
|
181
|
+
newQuery.offset = _value;
|
|
155
182
|
continue;
|
|
156
|
-
|
|
183
|
+
}
|
|
157
184
|
case 'order':
|
|
158
185
|
order.order = value;
|
|
159
186
|
continue;
|
|
@@ -167,9 +194,10 @@ function traverse(queryNode, colander=null, model) {
|
|
|
167
194
|
}
|
|
168
195
|
}
|
|
169
196
|
|
|
170
|
-
// "statics" override or set any query
|
|
171
|
-
if (
|
|
172
|
-
const staticClausesEntries = Object.entries(
|
|
197
|
+
// "statics" override or set any query in clauses:
|
|
198
|
+
if (filter !== null) {
|
|
199
|
+
const staticClausesEntries = Object.entries(filter.statics.clauses);
|
|
200
|
+
|
|
173
201
|
for (let entry of staticClausesEntries) {
|
|
174
202
|
const [clauseName, staticClauseValue] = entry;
|
|
175
203
|
|
|
@@ -191,6 +219,11 @@ function traverse(queryNode, colander=null, model) {
|
|
|
191
219
|
}
|
|
192
220
|
}
|
|
193
221
|
}
|
|
222
|
+
|
|
223
|
+
// Check for undefined clauses:
|
|
224
|
+
if (newQuery.limit === undefined && typeof filter.bounds.clauses.limit === 'object') {
|
|
225
|
+
newQuery.limit = filter.bounds.clauses.limit.max ?? BOUNDS.limit.max;
|
|
226
|
+
}
|
|
194
227
|
// Clauses\
|
|
195
228
|
|
|
196
229
|
|
|
@@ -229,27 +262,24 @@ function traverse(queryNode, colander=null, model) {
|
|
|
229
262
|
|
|
230
263
|
// Includes:
|
|
231
264
|
// If requested includes are not available:
|
|
232
|
-
const leftIncludes = includesAvailable.map(i => i.association);
|
|
233
265
|
for (let include of includes) {
|
|
234
266
|
const includeName = include.model;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
267
|
+
if (rootModelAssociations[includeName] === undefined) {
|
|
268
|
+
const err = new NQueryError(`No include named '${ includeName }'.`);
|
|
269
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
270
|
+
Error.captureStackTrace(err, traverse);
|
|
239
271
|
throw err;
|
|
240
272
|
}
|
|
241
|
-
|
|
242
|
-
leftIncludes.splice(includeIndex, 1);
|
|
243
273
|
}
|
|
244
274
|
|
|
245
|
-
_traverseIncludes(includes,
|
|
275
|
+
_traverseIncludes(includes, _model, filter, newQuery)
|
|
246
276
|
// Includes\
|
|
247
277
|
|
|
248
278
|
|
|
249
279
|
// Where:
|
|
250
280
|
const whereEntries = Object.entries(where);
|
|
251
281
|
for (let [attribute, value] of whereEntries) {
|
|
252
|
-
_parseWhereEntry(attribute, value, newQuery.where,
|
|
282
|
+
_parseWhereEntry(attribute, value, newQuery.where, filter.statics.attributes);
|
|
253
283
|
}
|
|
254
284
|
|
|
255
285
|
// If "where" was not set:
|
|
@@ -262,48 +292,30 @@ function traverse(queryNode, colander=null, model) {
|
|
|
262
292
|
}
|
|
263
293
|
|
|
264
294
|
|
|
265
|
-
function _traverseIncludes(includes, model,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
for (let include of includes) {
|
|
269
|
-
const includeName = include.model;
|
|
270
|
-
const association = model.associations[includeName];
|
|
295
|
+
function _traverseIncludes(includes, model, filter, resultQuery) {
|
|
296
|
+
const filterIncludesEntries = Object.entries(filter.includes);
|
|
297
|
+
for (let [ includeName, includeFilter ] of filterIncludesEntries) {
|
|
271
298
|
|
|
272
|
-
|
|
273
|
-
if (!association) {
|
|
274
|
-
const err = new TypeError(`No include ${ includeName }`);
|
|
275
|
-
throw err;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const includeModel = association.target;
|
|
279
|
-
// Build query for this include.
|
|
280
|
-
const associationQuery = traverse(include, null, includeModel);
|
|
299
|
+
const association = model.associations[includeName];
|
|
281
300
|
|
|
282
|
-
|
|
301
|
+
// If no such association:
|
|
302
|
+
if (!association) {
|
|
303
|
+
const err = new NQueryError(`No include named '${ includeName }'.`);
|
|
304
|
+
err.status = httpCodes.NOT_ACCEPTABLE;
|
|
305
|
+
Error.captureStackTrace(err, _traverseIncludes);
|
|
306
|
+
throw err;
|
|
283
307
|
}
|
|
284
|
-
}
|
|
285
|
-
// Colander is present:
|
|
286
|
-
else {
|
|
287
|
-
const colanderIncludeEntries = Object.entries(colander.includes);
|
|
288
|
-
for (let [includeName, includeColander] of colanderIncludeEntries) {
|
|
289
|
-
const association = model.associations[includeName];
|
|
290
|
-
// If no such association:
|
|
291
|
-
if (!association) {
|
|
292
|
-
const err = new TypeError(`No include ${ includeName }`);
|
|
293
|
-
throw err;
|
|
294
|
-
}
|
|
295
308
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
309
|
+
// If include was not requested:
|
|
310
|
+
const include = includes.find(({ model }) => model === includeName);
|
|
311
|
+
if (!include)
|
|
312
|
+
continue;
|
|
300
313
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
const includeModel = association.target;
|
|
315
|
+
// Build query for this include.
|
|
316
|
+
const associationQuery = traverse(include, filter.includes[includeName], includeModel);
|
|
304
317
|
|
|
305
|
-
|
|
306
|
-
}
|
|
318
|
+
_addAssociationQuery(associationQuery, includeName, resultQuery);
|
|
307
319
|
}
|
|
308
320
|
}
|
|
309
321
|
|
|
@@ -379,3 +391,32 @@ function _parseValue(value, attribute) {
|
|
|
379
391
|
|
|
380
392
|
return value;
|
|
381
393
|
}
|
|
394
|
+
|
|
395
|
+
function _setValueWithBounds(value, type, bounds) {
|
|
396
|
+
if (typeof bounds === 'object') {
|
|
397
|
+
|
|
398
|
+
switch(type) {
|
|
399
|
+
case 'number': {
|
|
400
|
+
let _value = value;
|
|
401
|
+
|
|
402
|
+
const {
|
|
403
|
+
min,
|
|
404
|
+
max
|
|
405
|
+
} = bounds;
|
|
406
|
+
|
|
407
|
+
const _min = isNaN(min) ? 1 : min;
|
|
408
|
+
_value = _value < _min ? _min : _value;
|
|
409
|
+
|
|
410
|
+
const _max = isNaN(max) ? 1 : max;
|
|
411
|
+
_value = _value > _max ? _max : _value;
|
|
412
|
+
|
|
413
|
+
return _value;
|
|
414
|
+
}
|
|
415
|
+
default:
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// If bounds were not set, just use original value.
|
|
421
|
+
return value;
|
|
422
|
+
}
|