axe-api 0.17.2 → 0.18.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/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- package/.github/workflows/auto-tag.yml +15 -0
- package/CHANGELOG.md +32 -0
- package/index.js +7 -1
- package/package.json +4 -4
- package/readme.md +1 -3
- package/src/constants.js +19 -10
- package/src/core/QueryParser.js +59 -62
- package/src/handlers/autosave.js +10 -2
- package/src/handlers/destroy.js +3 -1
- package/src/handlers/helpers.js +39 -30
- package/src/handlers/paginate.js +15 -2
- package/src/handlers/show.js +18 -3
- package/src/handlers/store.js +6 -1
- package/src/handlers/update.js +9 -2
- package/src/resolvers/setExpressRoutes.js +23 -25
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
<!--- Describe your changes in detail -->
|
|
4
|
+
|
|
5
|
+
## Motivation and Context
|
|
6
|
+
|
|
7
|
+
<!--- Why is this change required? What problem does it solve? -->
|
|
8
|
+
<!--- If it fixes an open issue, please link to the issue here. -->
|
|
9
|
+
|
|
10
|
+
## How has this been tested?
|
|
11
|
+
|
|
12
|
+
<!--- Please describe in detail how you tested your changes. -->
|
|
13
|
+
<!--- Include details of your testing environment, tests ran to see how -->
|
|
14
|
+
<!--- your change affects other areas of the code, etc. -->
|
|
15
|
+
|
|
16
|
+
## Types of changes
|
|
17
|
+
|
|
18
|
+
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
|
19
|
+
|
|
20
|
+
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
21
|
+
- [ ] New feature (non-breaking change which adds functionality)
|
|
22
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
23
|
+
|
|
24
|
+
## Checklist:
|
|
25
|
+
|
|
26
|
+
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
|
27
|
+
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
|
28
|
+
|
|
29
|
+
- [ ] My changes requires documentation change.
|
|
30
|
+
- [ ] The changes have been documented in the [axe-api/docs](https://github.com/axe-api/docs) repository. (<!--- Please insert the PR link -->)
|
|
31
|
+
- [ ] I added integration tests properly.
|
|
32
|
+
- [ ] The changes require [axe-api-template](https://github.com/axe-api/axe-api-template) changes. (<!--- Please insert the PR link -->)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## [0.18.0 (2021-11-30)](https://github.com/axe-api/axe-api/compare/0.18.0...0.17.5)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- [#115](https://github.com/axe-api/axe-api/issues/115)
|
|
8
|
+
- [#114](https://github.com/axe-api/axe-api/issues/114)
|
|
9
|
+
|
|
10
|
+
### Enhancements
|
|
11
|
+
|
|
12
|
+
- [#113](https://github.com/axe-api/axe-api/issues/113)
|
|
13
|
+
- [#107](https://github.com/axe-api/axe-api/issues/107)
|
|
14
|
+
- [#108](https://github.com/axe-api/axe-api/issues/108)
|
|
15
|
+
|
|
16
|
+
## [0.17.5 (2021-11-27)](https://github.com/axe-api/axe-api/compare/0.17.5...0.17.4)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- [#111](https://github.com/axe-api/axe-api/issues/111)
|
|
21
|
+
|
|
22
|
+
## [0.17.4 (2021-10-28)](https://github.com/axe-api/axe-api/compare/0.17.4...0.17.3)
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- [#97](https://github.com/axe-api/axe-api/issues/97)
|
|
27
|
+
- [#104](https://github.com/axe-api/axe-api/issues/104)
|
|
28
|
+
|
|
29
|
+
## [0.17.3 (2021-10-28)](https://github.com/axe-api/axe-api/compare/0.17.3...0.17.2)
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- [#98](https://github.com/axe-api/axe-api/issues/98)
|
|
34
|
+
|
|
3
35
|
## [0.17.2 (2021-10-17)](https://github.com/axe-api/axe-api/compare/0.17.2...0.17.1)
|
|
4
36
|
|
|
5
37
|
### Fixed
|
package/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import Server from "./src/Server.js";
|
|
2
2
|
import Model from "./src/core/Model.js";
|
|
3
3
|
import IoC from "./src/core/IoC.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
LOG_LEVEL,
|
|
6
|
+
HOOK_FUNCTIONS,
|
|
7
|
+
HANDLERS,
|
|
8
|
+
DEFAULT_HANDLERS,
|
|
9
|
+
} from "./src/constants.js";
|
|
5
10
|
import HttpResponse from "./src/core/HttpResponse.js";
|
|
6
11
|
|
|
7
12
|
export {
|
|
@@ -12,4 +17,5 @@ export {
|
|
|
12
17
|
LOG_LEVEL,
|
|
13
18
|
HOOK_FUNCTIONS,
|
|
14
19
|
HANDLERS,
|
|
20
|
+
DEFAULT_HANDLERS,
|
|
15
21
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axe-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "AXE API is a simple tool which has been created based on Express and Knex.js to create Rest APIs quickly.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"change-case": "^4.1.2",
|
|
23
23
|
"dotenv": "^10.0.0",
|
|
24
24
|
"express": "^4.17.1",
|
|
25
|
-
"knex": "^0.95.
|
|
26
|
-
"knex-paginate": "^
|
|
27
|
-
"knex-schema-inspector": "^1.6.
|
|
25
|
+
"knex": "^0.95.12",
|
|
26
|
+
"knex-paginate": "^3.0.0",
|
|
27
|
+
"knex-schema-inspector": "^1.6.4",
|
|
28
28
|
"pluralize": "^8.0.0",
|
|
29
29
|
"validatorjs": "^3.22.1"
|
|
30
30
|
},
|
package/readme.md
CHANGED
|
@@ -46,9 +46,7 @@ Fastest way to create simple Rest API by defining database models and relations.
|
|
|
46
46
|
Execute the following commands to prepare the integration app
|
|
47
47
|
|
|
48
48
|
```sh
|
|
49
|
-
cd tests/integrations && npm install
|
|
50
|
-
npm ci
|
|
51
|
-
npm run build --if-present
|
|
49
|
+
cd tests/integrations && npm install && npm ci && npm run build --if-present
|
|
52
50
|
```
|
|
53
51
|
|
|
54
52
|
Execute the following commands to prepare the database;
|
package/src/constants.js
CHANGED
|
@@ -55,6 +55,14 @@ const HANDLERS = {
|
|
|
55
55
|
AUTOSAVE: "autosave",
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
const DEFAULT_HANDLERS = [
|
|
59
|
+
HANDLERS.INSERT,
|
|
60
|
+
HANDLERS.PAGINATE,
|
|
61
|
+
HANDLERS.SHOW,
|
|
62
|
+
HANDLERS.UPDATE,
|
|
63
|
+
HANDLERS.DELETE,
|
|
64
|
+
];
|
|
65
|
+
|
|
58
66
|
const HTTP_METHODS = {
|
|
59
67
|
POST: "POST",
|
|
60
68
|
GET: "GET",
|
|
@@ -64,31 +72,31 @@ const HTTP_METHODS = {
|
|
|
64
72
|
|
|
65
73
|
const API_ROUTE_TEMPLATES = {
|
|
66
74
|
[HANDLERS.INSERT]: {
|
|
67
|
-
url: (parentUrl, resource) =>
|
|
75
|
+
url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
|
|
68
76
|
method: HTTP_METHODS.POST,
|
|
69
77
|
},
|
|
70
78
|
[HANDLERS.PAGINATE]: {
|
|
71
|
-
url: (parentUrl, resource) =>
|
|
79
|
+
url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
|
|
72
80
|
method: HTTP_METHODS.GET,
|
|
73
81
|
},
|
|
74
82
|
[HANDLERS.SHOW]: {
|
|
75
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
76
|
-
|
|
83
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
84
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
77
85
|
method: HTTP_METHODS.GET,
|
|
78
86
|
},
|
|
79
87
|
[HANDLERS.UPDATE]: {
|
|
80
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
81
|
-
|
|
88
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
89
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
82
90
|
method: HTTP_METHODS.PUT,
|
|
83
91
|
},
|
|
84
92
|
[HANDLERS.AUTOSAVE]: {
|
|
85
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
86
|
-
|
|
93
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
94
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}/autosave`,
|
|
87
95
|
method: HTTP_METHODS.PUT,
|
|
88
96
|
},
|
|
89
97
|
[HANDLERS.DELETE]: {
|
|
90
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
91
|
-
|
|
98
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
99
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
92
100
|
method: HTTP_METHODS.DELETE,
|
|
93
101
|
},
|
|
94
102
|
};
|
|
@@ -125,5 +133,6 @@ export {
|
|
|
125
133
|
LOG_COLORS,
|
|
126
134
|
DEPENDECY_TYPES,
|
|
127
135
|
HANDLERS,
|
|
136
|
+
DEFAULT_HANDLERS,
|
|
128
137
|
TIMESTAMP_COLUMNS,
|
|
129
138
|
};
|
package/src/core/QueryParser.js
CHANGED
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
import HttpResponse from "./../core/HttpResponse.js";
|
|
2
2
|
import { RELATIONSHIPS } from "../constants.js";
|
|
3
3
|
|
|
4
|
-
const DEFAULT_OPTIONS = {
|
|
5
|
-
min_per_page: 1,
|
|
6
|
-
max_per_page: 1000,
|
|
7
|
-
};
|
|
8
|
-
|
|
9
4
|
class QueryParser {
|
|
10
|
-
constructor({ model, models
|
|
5
|
+
constructor({ model, models }) {
|
|
11
6
|
this.model = model;
|
|
12
7
|
this.models = models;
|
|
13
8
|
this.createdJoins = [];
|
|
14
9
|
this.relationColumns = [];
|
|
15
10
|
this.usedConditionColumns = new Set();
|
|
16
|
-
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
17
|
-
|
|
18
|
-
if (isNaN(this.options.min_per_page) || this.options.min_per_page < 1) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
`You set unacceptable query parse option (min_per_page): ${this.options.min_per_page}`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (isNaN(this.options.max_per_page) || this.options.max_per_page > 10000) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`You set unacceptable query parse option (max_per_page): ${this.options.max_per_page}`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
11
|
}
|
|
30
12
|
|
|
31
13
|
applyFields(query, fields) {
|
|
@@ -109,10 +91,9 @@ class QueryParser {
|
|
|
109
91
|
});
|
|
110
92
|
|
|
111
93
|
if (undefinedColumns.length > 0) {
|
|
112
|
-
throw new HttpResponse(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
);
|
|
94
|
+
throw new HttpResponse(400, {
|
|
95
|
+
message: `Undefined column names: ${undefinedColumns.join(",")}`,
|
|
96
|
+
});
|
|
116
97
|
}
|
|
117
98
|
|
|
118
99
|
return conditions;
|
|
@@ -215,7 +196,9 @@ class QueryParser {
|
|
|
215
196
|
try {
|
|
216
197
|
sections.q = JSON.parse(queryContent);
|
|
217
198
|
} catch (err) {
|
|
218
|
-
throw new
|
|
199
|
+
throw new HttpResponse(400, {
|
|
200
|
+
message: `Unacceptable query string: ${queryContent}`,
|
|
201
|
+
});
|
|
219
202
|
}
|
|
220
203
|
}
|
|
221
204
|
|
|
@@ -247,16 +230,8 @@ class QueryParser {
|
|
|
247
230
|
_parsePerPage(content) {
|
|
248
231
|
const value = parseInt(content);
|
|
249
232
|
|
|
250
|
-
if (isNaN(value)) {
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (value <= this.options.min_per_page) {
|
|
255
|
-
return this.options.min_per_page;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (value > this.options.max_per_page) {
|
|
259
|
-
return this.options.max_per_page;
|
|
233
|
+
if (isNaN(value) || value <= 1 || value > 10000) {
|
|
234
|
+
return 10;
|
|
260
235
|
}
|
|
261
236
|
|
|
262
237
|
return value;
|
|
@@ -325,19 +300,24 @@ class QueryParser {
|
|
|
325
300
|
return null;
|
|
326
301
|
}
|
|
327
302
|
|
|
303
|
+
const wheres = [];
|
|
304
|
+
for (const key in content) {
|
|
305
|
+
wheres.push(this._parseConditionObject(content, key));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return wheres;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_parseConditionObject(content, key) {
|
|
328
312
|
const where = {
|
|
329
313
|
prefix: null,
|
|
330
314
|
model: this.model,
|
|
331
315
|
table: this.model.instance.table,
|
|
332
|
-
field:
|
|
316
|
+
field: key,
|
|
333
317
|
condition: "=",
|
|
334
|
-
value:
|
|
318
|
+
value: content[key],
|
|
335
319
|
};
|
|
336
320
|
|
|
337
|
-
const key = Object.keys(content)[0];
|
|
338
|
-
where.field = key;
|
|
339
|
-
where.value = content[key];
|
|
340
|
-
|
|
341
321
|
// Sometimes we can have basic OR operations for queries
|
|
342
322
|
if (where.field.indexOf("$or.") === 0) {
|
|
343
323
|
where.prefix = "or";
|
|
@@ -349,19 +329,30 @@ class QueryParser {
|
|
|
349
329
|
where.field = where.field.replace("$and.", "");
|
|
350
330
|
}
|
|
351
331
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
332
|
+
// If there is not any value, it means that we should check nullable values
|
|
333
|
+
if (where.value === null) {
|
|
334
|
+
// If the client wants to see not nullable values
|
|
335
|
+
if (this._hasSpecialStructure(where.field, ".$not")) {
|
|
336
|
+
where.field = where.field.replace(".$not", "");
|
|
337
|
+
where.condition = "NotNull";
|
|
338
|
+
} else {
|
|
339
|
+
// So, it means that the clients wants to see null valus
|
|
340
|
+
where.condition = "Null";
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// If there is value, we should check it
|
|
344
|
+
this._applySpecialCondition(where, "$not", "<>");
|
|
345
|
+
this._applySpecialCondition(where, "$gt", ">");
|
|
346
|
+
this._applySpecialCondition(where, "$gte", ">=");
|
|
347
|
+
this._applySpecialCondition(where, "$lt", "<");
|
|
348
|
+
this._applySpecialCondition(where, "$lte", "<=");
|
|
349
|
+
this._applySpecialCondition(where, "$like", "LIKE");
|
|
350
|
+
this._applySpecialCondition(where, "$notLike", "NOT LIKE");
|
|
351
|
+
this._applySpecialCondition(where, "$in", "In");
|
|
352
|
+
this._applySpecialCondition(where, "$notIn", "NotIn");
|
|
353
|
+
this._applySpecialCondition(where, "$between", "Between");
|
|
354
|
+
this._applySpecialCondition(where, "$notBetween", "NotBetween");
|
|
355
|
+
}
|
|
365
356
|
|
|
366
357
|
if (where.condition === "In" || where.condition === "NotIn") {
|
|
367
358
|
where.value = where.value.split(",");
|
|
@@ -371,10 +362,6 @@ class QueryParser {
|
|
|
371
362
|
where.value = where.value.split(":");
|
|
372
363
|
}
|
|
373
364
|
|
|
374
|
-
if (where.condition === "Null" || where.condition === "NotNull") {
|
|
375
|
-
where.value = null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
365
|
if (where.condition === "LIKE" || where.condition === "NOT LIKE") {
|
|
379
366
|
where.value = where.value.replace(/\*/g, "%");
|
|
380
367
|
}
|
|
@@ -389,7 +376,9 @@ class QueryParser {
|
|
|
389
376
|
);
|
|
390
377
|
|
|
391
378
|
if (!relation) {
|
|
392
|
-
throw new
|
|
379
|
+
throw new HttpResponse(400, {
|
|
380
|
+
message: `Unacceptable query field: ${relationName}.${field}`,
|
|
381
|
+
});
|
|
393
382
|
}
|
|
394
383
|
|
|
395
384
|
const relatedModel = this.models.find(
|
|
@@ -397,7 +386,9 @@ class QueryParser {
|
|
|
397
386
|
);
|
|
398
387
|
|
|
399
388
|
if (!relatedModel) {
|
|
400
|
-
throw new
|
|
389
|
+
throw new HttpResponse(400, {
|
|
390
|
+
message: `Undefined model name: ${relation.model}`,
|
|
391
|
+
});
|
|
401
392
|
}
|
|
402
393
|
|
|
403
394
|
where.model = relatedModel;
|
|
@@ -504,7 +495,9 @@ class QueryParser {
|
|
|
504
495
|
(i) => i.name === item.relationship
|
|
505
496
|
);
|
|
506
497
|
if (!relation) {
|
|
507
|
-
throw new
|
|
498
|
+
throw new HttpResponse(400, {
|
|
499
|
+
message: `Undefined relation: ${item.relationship}`,
|
|
500
|
+
});
|
|
508
501
|
}
|
|
509
502
|
|
|
510
503
|
this.relationColumns.push(
|
|
@@ -535,11 +528,15 @@ class QueryParser {
|
|
|
535
528
|
_shouldBeAcceptableColumn(field) {
|
|
536
529
|
const regex = /^[0-9,a-z,A-Z_.]+$/;
|
|
537
530
|
if (!field.match(regex)) {
|
|
538
|
-
throw new
|
|
531
|
+
throw new HttpResponse(400, {
|
|
532
|
+
message: `Unacceptable field name: ${field}`,
|
|
533
|
+
});
|
|
539
534
|
}
|
|
540
535
|
|
|
541
536
|
if (field.indexOf(".") === 0 || field.indexOf(".") === field.length - 1) {
|
|
542
|
-
throw new
|
|
537
|
+
throw new HttpResponse(400, {
|
|
538
|
+
message: `You have to define the column specefically: ${field}`,
|
|
539
|
+
});
|
|
543
540
|
}
|
|
544
541
|
}
|
|
545
542
|
}
|
package/src/handlers/autosave.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import Validator from "validatorjs";
|
|
12
12
|
import { HOOK_FUNCTIONS, TIMESTAMP_COLUMNS } from "./../constants.js";
|
|
13
13
|
import HttpResponse from "./../core/HttpResponse.js";
|
|
14
|
+
import { HANDLERS } from "./../constants.js";
|
|
14
15
|
|
|
15
16
|
export default async (context) => {
|
|
16
17
|
const { request, response, model, trx, relation, parentModel } = context;
|
|
@@ -29,7 +30,9 @@ export default async (context) => {
|
|
|
29
30
|
.where(model.instance.primaryKey, request.params[model.instance.primaryKey])
|
|
30
31
|
.first();
|
|
31
32
|
if (!item) {
|
|
32
|
-
throw new HttpResponse(404,
|
|
33
|
+
throw new HttpResponse(404, {
|
|
34
|
+
message: `The item is not found on ${model.name}.`,
|
|
35
|
+
});
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
await callHooks(model, HOOK_FUNCTIONS.onAfterUpdateQuery, {
|
|
@@ -78,7 +81,12 @@ export default async (context) => {
|
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
// Serializing the data by the model's serialize method
|
|
81
|
-
item = serializeData(
|
|
84
|
+
item = await serializeData(
|
|
85
|
+
item,
|
|
86
|
+
model.instance.serialize,
|
|
87
|
+
HANDLERS.AUTOSAVE,
|
|
88
|
+
request
|
|
89
|
+
);
|
|
82
90
|
|
|
83
91
|
// Filtering hidden fields from the response data.
|
|
84
92
|
filterHiddenFields([item], model.instance.hiddens);
|
package/src/handlers/destroy.js
CHANGED
|
@@ -22,7 +22,9 @@ export default async (context) => {
|
|
|
22
22
|
|
|
23
23
|
let item = await query.first();
|
|
24
24
|
if (!item) {
|
|
25
|
-
throw new HttpResponse(404,
|
|
25
|
+
throw new HttpResponse(404, {
|
|
26
|
+
message: `The item is not found on ${model.name}.`,
|
|
27
|
+
});
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
await callHooks(model, HOOK_FUNCTIONS.onAfterDeleteQuery, {
|
package/src/handlers/helpers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RELATIONSHIPS } from "./../constants.js";
|
|
2
2
|
import { camelCase } from "change-case";
|
|
3
3
|
import HttpResponse from "./../core/HttpResponse.js";
|
|
4
|
-
import IoC from
|
|
4
|
+
import IoC from "../core/IoC.js";
|
|
5
5
|
|
|
6
6
|
const getInputFromBody = (body, field) => {
|
|
7
7
|
if (!body) {
|
|
@@ -83,7 +83,8 @@ export const getRelatedData = async (
|
|
|
83
83
|
model,
|
|
84
84
|
models,
|
|
85
85
|
database,
|
|
86
|
-
handler
|
|
86
|
+
handler,
|
|
87
|
+
request
|
|
87
88
|
) => {
|
|
88
89
|
if (withArray.length === 0) {
|
|
89
90
|
return;
|
|
@@ -96,10 +97,9 @@ export const getRelatedData = async (
|
|
|
96
97
|
(relation) => relation.name === clientQuery.relationship
|
|
97
98
|
);
|
|
98
99
|
if (!definedRelation) {
|
|
99
|
-
throw new HttpResponse(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
100
|
+
throw new HttpResponse(400, {
|
|
101
|
+
message: `Undefined relation: ${clientQuery.relationship}`,
|
|
102
|
+
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Find the foreign model by the relationship
|
|
@@ -164,10 +164,9 @@ export const getRelatedData = async (
|
|
|
164
164
|
(column) => !foreignModel.instance.columnNames.includes(column)
|
|
165
165
|
);
|
|
166
166
|
if (undefinedColumns.length > 0) {
|
|
167
|
-
throw new HttpResponse(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
);
|
|
167
|
+
throw new HttpResponse(400, {
|
|
168
|
+
message: `Undefined columns: ${undefinedColumns.join(", ")}`,
|
|
169
|
+
});
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
|
|
@@ -189,7 +188,8 @@ export const getRelatedData = async (
|
|
|
189
188
|
relatedRecords = await serializeData(
|
|
190
189
|
relatedRecords,
|
|
191
190
|
foreignModel.instance.serialize,
|
|
192
|
-
handler
|
|
191
|
+
handler,
|
|
192
|
+
request
|
|
193
193
|
);
|
|
194
194
|
|
|
195
195
|
// We should hide hidden fields if there is any
|
|
@@ -203,7 +203,8 @@ export const getRelatedData = async (
|
|
|
203
203
|
foreignModel,
|
|
204
204
|
models,
|
|
205
205
|
database,
|
|
206
|
-
handler
|
|
206
|
+
handler,
|
|
207
|
+
request
|
|
207
208
|
);
|
|
208
209
|
}
|
|
209
210
|
|
|
@@ -243,19 +244,19 @@ export const bindTimestampValues = (formData, columnTypes = [], model) => {
|
|
|
243
244
|
}
|
|
244
245
|
};
|
|
245
246
|
|
|
246
|
-
const serialize = async (data, callback) => {
|
|
247
|
+
const serialize = async (data, callback, request) => {
|
|
247
248
|
if (!callback) {
|
|
248
249
|
return data;
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
if (Array.isArray(data)) {
|
|
252
|
-
return data.map(callback);
|
|
253
|
+
return data.map((item) => callback(item, request));
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
return
|
|
256
|
-
}
|
|
256
|
+
return callback(data, request);
|
|
257
|
+
};
|
|
257
258
|
|
|
258
|
-
const globalSerializer = async (itemArray, handler) => {
|
|
259
|
+
const globalSerializer = async (itemArray, handler, request) => {
|
|
259
260
|
const { Application } = await IoC.use("Config");
|
|
260
261
|
|
|
261
262
|
if (!Application.serializers) {
|
|
@@ -263,35 +264,43 @@ const globalSerializer = async (itemArray, handler) => {
|
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
let callbacks = [];
|
|
266
|
-
// Push all runable serializer into callbacks.
|
|
267
|
-
Application.serializers.map(configSerializer => {
|
|
267
|
+
// Push all runable serializer into callbacks.
|
|
268
|
+
Application.serializers.map((configSerializer) => {
|
|
268
269
|
// Serialize data for all requests types.
|
|
269
270
|
if (typeof configSerializer === "function") {
|
|
270
271
|
callbacks.push(configSerializer);
|
|
271
|
-
return
|
|
272
|
+
return;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
// Serialize data with specific handler like "PAGINATE" or "SHOW".
|
|
275
|
-
if (
|
|
276
|
+
if (
|
|
277
|
+
typeof configSerializer === "object" &&
|
|
278
|
+
configSerializer.handler.includes(handler)
|
|
279
|
+
) {
|
|
276
280
|
// Handle multiple serializer.
|
|
277
281
|
if (Array.isArray(configSerializer.serializer)) {
|
|
278
|
-
configSerializer.serializer.forEach(fn => callbacks.push(fn));
|
|
279
|
-
return
|
|
282
|
+
configSerializer.serializer.forEach((fn) => callbacks.push(fn));
|
|
283
|
+
return;
|
|
280
284
|
}
|
|
281
|
-
callbacks.push(configSerializer.serializer)
|
|
282
|
-
return
|
|
285
|
+
callbacks.push(configSerializer.serializer);
|
|
286
|
+
return;
|
|
283
287
|
}
|
|
284
|
-
})
|
|
288
|
+
});
|
|
285
289
|
|
|
286
290
|
while (callbacks.length !== 0) {
|
|
287
|
-
itemArray =
|
|
291
|
+
itemArray = serialize(itemArray, callbacks.shift(), request);
|
|
288
292
|
}
|
|
289
293
|
return itemArray;
|
|
290
294
|
};
|
|
291
295
|
|
|
292
|
-
export const serializeData = async (
|
|
293
|
-
itemArray
|
|
294
|
-
|
|
296
|
+
export const serializeData = async (
|
|
297
|
+
itemArray,
|
|
298
|
+
modelSerializer,
|
|
299
|
+
handler,
|
|
300
|
+
request
|
|
301
|
+
) => {
|
|
302
|
+
itemArray = serialize(itemArray, modelSerializer, request);
|
|
303
|
+
itemArray = await globalSerializer(itemArray, handler, request);
|
|
295
304
|
return itemArray;
|
|
296
305
|
};
|
|
297
306
|
|
package/src/handlers/paginate.js
CHANGED
|
@@ -45,7 +45,15 @@ export default async (context) => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// We should try to get related data if there is any
|
|
48
|
-
await getRelatedData(
|
|
48
|
+
await getRelatedData(
|
|
49
|
+
result.data,
|
|
50
|
+
conditions.with,
|
|
51
|
+
model,
|
|
52
|
+
models,
|
|
53
|
+
trx,
|
|
54
|
+
HANDLERS.PAGINATE,
|
|
55
|
+
request
|
|
56
|
+
);
|
|
49
57
|
|
|
50
58
|
await callHooks(model, HOOK_FUNCTIONS.onAfterPaginate, {
|
|
51
59
|
...context,
|
|
@@ -55,7 +63,12 @@ export default async (context) => {
|
|
|
55
63
|
});
|
|
56
64
|
|
|
57
65
|
// Serializing the data by the model's serialize method
|
|
58
|
-
result.data = await serializeData(
|
|
66
|
+
result.data = await serializeData(
|
|
67
|
+
result.data,
|
|
68
|
+
model.instance.serialize,
|
|
69
|
+
HANDLERS.PAGINATE,
|
|
70
|
+
request
|
|
71
|
+
);
|
|
59
72
|
|
|
60
73
|
// Filtering hidden fields from the response data.
|
|
61
74
|
filterHiddenFields(result.data, model.instance.hiddens);
|
package/src/handlers/show.js
CHANGED
|
@@ -43,11 +43,21 @@ export default async (context) => {
|
|
|
43
43
|
|
|
44
44
|
let item = await query.first();
|
|
45
45
|
if (!item) {
|
|
46
|
-
throw new HttpResponse(404,
|
|
46
|
+
throw new HttpResponse(404, {
|
|
47
|
+
message: `The item is not found on ${model.name}.`,
|
|
48
|
+
});
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
// We should try to get related data if there is any
|
|
50
|
-
await getRelatedData(
|
|
52
|
+
await getRelatedData(
|
|
53
|
+
[item],
|
|
54
|
+
conditions.with,
|
|
55
|
+
model,
|
|
56
|
+
models,
|
|
57
|
+
trx,
|
|
58
|
+
HANDLERS.SHOW,
|
|
59
|
+
request
|
|
60
|
+
);
|
|
51
61
|
|
|
52
62
|
await callHooks(model, HOOK_FUNCTIONS.onAfterShow, {
|
|
53
63
|
...context,
|
|
@@ -57,7 +67,12 @@ export default async (context) => {
|
|
|
57
67
|
});
|
|
58
68
|
|
|
59
69
|
// Serializing the data by the model's serialize method
|
|
60
|
-
item = await serializeData(
|
|
70
|
+
item = await serializeData(
|
|
71
|
+
item,
|
|
72
|
+
model.instance.serialize,
|
|
73
|
+
HANDLERS.SHOW,
|
|
74
|
+
request
|
|
75
|
+
);
|
|
61
76
|
|
|
62
77
|
// Filtering hidden fields from the response data.
|
|
63
78
|
filterHiddenFields([item], model.instance.hiddens);
|
package/src/handlers/store.js
CHANGED
|
@@ -63,7 +63,12 @@ export default async (context) => {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
// Serializing the data by the model's serialize method
|
|
66
|
-
item = await serializeData(
|
|
66
|
+
item = await serializeData(
|
|
67
|
+
item,
|
|
68
|
+
model.instance.serialize,
|
|
69
|
+
HANDLERS.INSERT,
|
|
70
|
+
request
|
|
71
|
+
);
|
|
67
72
|
|
|
68
73
|
// Filtering hidden fields from the response data.
|
|
69
74
|
filterHiddenFields([item], model.instance.hiddens);
|
package/src/handlers/update.js
CHANGED
|
@@ -28,7 +28,9 @@ export default async (context) => {
|
|
|
28
28
|
.where(model.instance.primaryKey, request.params[model.instance.primaryKey])
|
|
29
29
|
.first();
|
|
30
30
|
if (!item) {
|
|
31
|
-
throw new HttpResponse(404,
|
|
31
|
+
throw new HttpResponse(404, {
|
|
32
|
+
message: `The item is not found on ${model.name}.`,
|
|
33
|
+
});
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
await callHooks(model, HOOK_FUNCTIONS.onAfterUpdateQuery, {
|
|
@@ -76,7 +78,12 @@ export default async (context) => {
|
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
// Serializing the data by the model's serialize method
|
|
79
|
-
item = await serializeData(
|
|
81
|
+
item = await serializeData(
|
|
82
|
+
item,
|
|
83
|
+
model.instance.serialize,
|
|
84
|
+
HANDLERS.UPDATE,
|
|
85
|
+
request
|
|
86
|
+
);
|
|
80
87
|
|
|
81
88
|
// Filtering hidden fields from the response data.
|
|
82
89
|
filterHiddenFields([item], model.instance.hiddens);
|
|
@@ -1,29 +1,11 @@
|
|
|
1
1
|
import IoC from "./../core/IoC.js";
|
|
2
2
|
import pluralize from "pluralize";
|
|
3
|
+
import { paramCase } from "change-case";
|
|
3
4
|
import { RELATIONSHIPS, API_ROUTE_TEMPLATES } from "./../constants.js";
|
|
4
5
|
import Handlers from "./../handlers/index.js";
|
|
5
6
|
|
|
6
7
|
let Config = null;
|
|
7
8
|
|
|
8
|
-
const handleErrors = (req, res, error) => {
|
|
9
|
-
const status = error.type === "HttpResponse" ? error.status : 400;
|
|
10
|
-
let errors = error.content ? error.content : error.message;
|
|
11
|
-
|
|
12
|
-
if (Config.Application.env === "production") {
|
|
13
|
-
errors = "An error occurred!";
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const result = {
|
|
17
|
-
errors,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
if (Config.Application.env === "development") {
|
|
21
|
-
result.stack = error.stack;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
res.status(status).json(result);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
9
|
const setTransactionOption = (option, handler, defaultValue) => {
|
|
28
10
|
if (Array.isArray(option)) {
|
|
29
11
|
if (option.some((i) => i.handler === handler)) {
|
|
@@ -52,7 +34,7 @@ const hasTransaction = (config, model, handler) => {
|
|
|
52
34
|
return privilegedOption;
|
|
53
35
|
};
|
|
54
36
|
|
|
55
|
-
const requestHandler = async (handler, req, res, context) => {
|
|
37
|
+
const requestHandler = async (handler, req, res, next, context) => {
|
|
56
38
|
try {
|
|
57
39
|
context.trx = context.database;
|
|
58
40
|
if (hasTransaction(Config, context.model, handler)) {
|
|
@@ -72,7 +54,11 @@ const requestHandler = async (handler, req, res, context) => {
|
|
|
72
54
|
await context.trx.rollback();
|
|
73
55
|
}
|
|
74
56
|
|
|
75
|
-
|
|
57
|
+
if (error.type === "HttpResponse") {
|
|
58
|
+
return res.status(error.status).json(error.content);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
next(error);
|
|
76
62
|
}
|
|
77
63
|
};
|
|
78
64
|
|
|
@@ -83,7 +69,7 @@ const ucFirst = (string) => {
|
|
|
83
69
|
const getResourcePath = (model, relation) => {
|
|
84
70
|
return relation
|
|
85
71
|
? relation.resource
|
|
86
|
-
: pluralize.plural(model.name).toLowerCase();
|
|
72
|
+
: paramCase(pluralize.plural(model.name)).toLowerCase();
|
|
87
73
|
};
|
|
88
74
|
|
|
89
75
|
const getPrimaryKeyName = (model) => {
|
|
@@ -178,6 +164,13 @@ const getModelMiddlewares = (model, handler) => {
|
|
|
178
164
|
return middlewares;
|
|
179
165
|
};
|
|
180
166
|
|
|
167
|
+
const getRootPrefix = () => {
|
|
168
|
+
if (Config.Application.prefix) {
|
|
169
|
+
return Config.Application.prefix.replace(/^\/|\/$/g, "");
|
|
170
|
+
}
|
|
171
|
+
return "api";
|
|
172
|
+
};
|
|
173
|
+
|
|
181
174
|
const createRouteByModel = async (
|
|
182
175
|
model,
|
|
183
176
|
models,
|
|
@@ -215,6 +208,7 @@ const createRouteByModel = async (
|
|
|
215
208
|
|
|
216
209
|
const routeTemplate = API_ROUTE_TEMPLATES[handler];
|
|
217
210
|
const url = routeTemplate.url(
|
|
211
|
+
getRootPrefix(),
|
|
218
212
|
urlPrefix,
|
|
219
213
|
resource,
|
|
220
214
|
model.instance.primaryKey
|
|
@@ -228,9 +222,13 @@ const createRouteByModel = async (
|
|
|
228
222
|
docs.push(routeTemplate.method, url, model);
|
|
229
223
|
|
|
230
224
|
// Adding the route to the express
|
|
231
|
-
app[routeTemplate.method.toLowerCase()](
|
|
232
|
-
|
|
233
|
-
|
|
225
|
+
app[routeTemplate.method.toLowerCase()](
|
|
226
|
+
url,
|
|
227
|
+
middlewares,
|
|
228
|
+
(req, res, next) => {
|
|
229
|
+
requestHandler(handler, req, res, next, context);
|
|
230
|
+
}
|
|
231
|
+
);
|
|
234
232
|
}
|
|
235
233
|
|
|
236
234
|
await createChildRoutes(model, models, resource, urlPrefix);
|