axe-api 0.17.5 → 0.19.1
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/workflows/test-integration.yml +1 -1
- package/.github/workflows/test-unit.yml +1 -1
- package/CHANGELOG.md +35 -0
- package/package.json +17 -17
- package/readme.md +89 -12
- package/src/constants.js +23 -13
- package/src/core/Model.js +2 -3
- package/src/core/QueryParser.js +59 -62
- package/src/handlers/all.js +73 -0
- package/src/handlers/destroy.js +3 -1
- package/src/handlers/helpers.js +39 -30
- package/src/handlers/index.js +3 -2
- package/src/handlers/paginate.js +15 -2
- package/src/handlers/{autosave.js → patch.js} +10 -2
- package/src/handlers/show.js +18 -3
- package/src/handlers/store.js +13 -3
- package/src/handlers/update.js +9 -2
- package/src/resolvers/setExpressRoutes.js +22 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## [0.19.1 (2022-01-22)](https://github.com/axe-api/axe-api/compare/0.19.1...0.19.0)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- knex.js version update.
|
|
8
|
+
|
|
9
|
+
## [0.19.0 (2021-12-05)](https://github.com/axe-api/axe-api/compare/0.19.0...0.18.1)
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- [#110](https://github.com/axe-api/axe-api/issues/110)
|
|
14
|
+
|
|
15
|
+
### Enhancements
|
|
16
|
+
|
|
17
|
+
- [#106](https://github.com/axe-api/axe-api/issues/106)
|
|
18
|
+
|
|
19
|
+
## [0.18.1 (2021-12-02)](https://github.com/axe-api/axe-api/compare/0.18.1...0.18.0)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- [#117](https://github.com/axe-api/axe-api/issues/117)
|
|
24
|
+
|
|
25
|
+
## [0.18.0 (2021-11-30)](https://github.com/axe-api/axe-api/compare/0.18.0...0.17.5)
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- [#115](https://github.com/axe-api/axe-api/issues/115)
|
|
30
|
+
- [#114](https://github.com/axe-api/axe-api/issues/114)
|
|
31
|
+
|
|
32
|
+
### Enhancements
|
|
33
|
+
|
|
34
|
+
- [#113](https://github.com/axe-api/axe-api/issues/113)
|
|
35
|
+
- [#107](https://github.com/axe-api/axe-api/issues/107)
|
|
36
|
+
- [#108](https://github.com/axe-api/axe-api/issues/108)
|
|
37
|
+
|
|
3
38
|
## [0.17.5 (2021-11-27)](https://github.com/axe-api/axe-api/compare/0.17.5...0.17.4)
|
|
4
39
|
|
|
5
40
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axe-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
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",
|
|
@@ -20,37 +20,37 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"change-case": "^4.1.2",
|
|
23
|
-
"dotenv": "^
|
|
24
|
-
"express": "^4.17.
|
|
25
|
-
"knex": "^0.
|
|
23
|
+
"dotenv": "^14.2.0",
|
|
24
|
+
"express": "^4.17.2",
|
|
25
|
+
"knex": "^1.0.1",
|
|
26
26
|
"knex-paginate": "^3.0.0",
|
|
27
|
-
"knex-schema-inspector": "^1.
|
|
27
|
+
"knex-schema-inspector": "^1.7.1",
|
|
28
28
|
"pluralize": "^8.0.0",
|
|
29
29
|
"validatorjs": "^3.22.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@babel/cli": "^7.
|
|
33
|
-
"@babel/core": "^7.
|
|
34
|
-
"@babel/node": "^7.
|
|
35
|
-
"@babel/preset-env": "^7.
|
|
36
|
-
"@babel/runtime": "^7.
|
|
37
|
-
"babel-jest": "^27.
|
|
38
|
-
"babel-loader": "^8.2.
|
|
32
|
+
"@babel/cli": "^7.16.8",
|
|
33
|
+
"@babel/core": "^7.16.10",
|
|
34
|
+
"@babel/node": "^7.16.8",
|
|
35
|
+
"@babel/preset-env": "^7.16.11",
|
|
36
|
+
"@babel/runtime": "^7.16.7",
|
|
37
|
+
"babel-jest": "^27.4.6",
|
|
38
|
+
"babel-loader": "^8.2.3",
|
|
39
39
|
"babel-plugin-module-resolver": "^4.1.0",
|
|
40
40
|
"babel-preset-minify": "^0.5.1",
|
|
41
41
|
"eslint": "^7.31.0",
|
|
42
42
|
"eslint-config-standard": "^16.0.3",
|
|
43
|
-
"eslint-plugin-import": "^2.
|
|
43
|
+
"eslint-plugin-import": "^2.25.4",
|
|
44
44
|
"eslint-plugin-node": "^11.1.0",
|
|
45
45
|
"eslint-plugin-promise": "^5.1.0",
|
|
46
46
|
"eslint-plugin-standard": "^5.0.0",
|
|
47
47
|
"eslint-plugin-unicorn": "^33.0.1",
|
|
48
48
|
"eslint-watch": "^7.0.0",
|
|
49
|
-
"jest": "^27.
|
|
49
|
+
"jest": "^27.4.7",
|
|
50
50
|
"mysql": "^2.18.1",
|
|
51
|
-
"nodemon": "^2.0.
|
|
52
|
-
"pg": "^8.
|
|
53
|
-
"set-value": ">=4.
|
|
51
|
+
"nodemon": "^2.0.15",
|
|
52
|
+
"pg": "^8.7.1",
|
|
53
|
+
"set-value": ">=4.1.0",
|
|
54
54
|
"sqlite3": "^5.0.2"
|
|
55
55
|
}
|
|
56
56
|
}
|
package/readme.md
CHANGED
|
@@ -23,21 +23,98 @@
|
|
|
23
23
|
</a>
|
|
24
24
|
</h1>
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
The fastest way to create Rest API, by defining database models and relations.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
> Axe API has great documentation. Please [check it out in here](https://axe-api.github.io/).
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## What Is Axe API?
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
32
|
+
**Axe API** is the _fastest_ way to create **Rest API** by defining only database models and relationships between them. It is built on [Knex.js](http://knexjs.org), and its awesome active records pattern. On the other hand, you have another familiar thing, [Express](https://expressjs.com/).
|
|
33
|
+
|
|
34
|
+
You are going to be able to develop an API **10 times faster** with **Axe API**!
|
|
35
|
+
|
|
36
|
+
## How It Works?
|
|
37
|
+
|
|
38
|
+
[Express](https://expressjs.com/) and [Knex.js](http://knexjs.org) are great tools to create [Node.js](https://nodejs.org) based applications. But usually, we code too much the same things to design an API. We aim to reduce code duplication and give you speed by using Axe API.
|
|
39
|
+
|
|
40
|
+
Axe API provides you the ability to separate your common tasks to build an API from your business logic. **Axe API** expects model definitions to analyze your routing structure. After you created your models and their relations between them, Axe API can handle all _well-known_ API requests. Creating an API with 5 tables takes almost 15 minutes.
|
|
41
|
+
|
|
42
|
+
Shortly, **Axe API** performs three basic functions;
|
|
43
|
+
|
|
44
|
+
- **Analyzes** your models and their relationships to create routes.
|
|
45
|
+
- **Handles** all HTTP requests.
|
|
46
|
+
- **Separate** your business logic from API best practices.
|
|
47
|
+
|
|
48
|
+
Let's assume that you have a model like this;
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { Model } from "axe-api";
|
|
52
|
+
|
|
53
|
+
class User extends Model {}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
With this model, you will have all of the basic API routes for **User** resources. **Axe API** will create **CRUD** routes for you in the _booting_ process and these routes would be completely ready to be handled and processed by Axe API. The following routes will be handled automatically;
|
|
57
|
+
|
|
58
|
+
- `POST api/users`
|
|
59
|
+
- `GET api/users`
|
|
60
|
+
- `GET api/users/:id`
|
|
61
|
+
- `PUT api/users/:id`
|
|
62
|
+
- `DELETE api/users/:id`
|
|
63
|
+
|
|
64
|
+
This is the magic of **Axe API**!
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
Using **Axe API** in an application is very easy. We've created a CLI tool for you; [axe-magic](https://github.com/axe-api/axe-magic).
|
|
69
|
+
|
|
70
|
+
You can create a new Axe API project by using [axe-magic](https://github.com/axe-api/axe-magic). But first, you can install it in your development environment. When you installed it, you can be able to access **axe-magic** command via CLI. You can use the following command to install **axe-magic** to your machine;
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
$ npm i -g axe-magic
|
|
74
|
+
$ axe-magic --version
|
|
75
|
+
1.0.0
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
After that, creating a new project is very easy. Just you can execute the following command;
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
$ axe-magic new my-api
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This command will pull [axe-api-template](https://github.com/axe-api/axe-api-template) project to your current directory with a new name, **my-api**.
|
|
85
|
+
|
|
86
|
+
To install your project's depencies, you can execute the following commands in the root directory;
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
$ cd my-api
|
|
90
|
+
$ npm install
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To serve this application, you can execute the following command;
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
$ npm run start:dev
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> `start:dev` command use [nodemon](https://www.npmjs.com/package/nodemon). If you haven't installed it yet, we suggest you install it first.
|
|
100
|
+
|
|
101
|
+
After that, your first **Axe API** application will be running in `localhost:3000`.
|
|
102
|
+
|
|
103
|
+
You will see the following API response if you visit [localhost:3000](http://localhost:3000).
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"name": "AXE API",
|
|
108
|
+
"description": "The best API creation tool in the world.",
|
|
109
|
+
"aim": "To kill them all!"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
If you can see that response, it means that your project is running properly.
|
|
114
|
+
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
Axe API has great documentation. Please [check it out in here](https://axe-api.github.io/).
|
|
41
118
|
|
|
42
119
|
## How To Run Integration Tests
|
|
43
120
|
|
package/src/constants.js
CHANGED
|
@@ -13,6 +13,7 @@ const HOOK_FUNCTIONS = {
|
|
|
13
13
|
onBeforeDeleteQuery: "onBeforeDeleteQuery",
|
|
14
14
|
onBeforeDelete: "onBeforeDelete",
|
|
15
15
|
onBeforePaginate: "onBeforePaginate",
|
|
16
|
+
onBeforeAll: "onBeforeAll",
|
|
16
17
|
onBeforeShow: "onBeforeShow",
|
|
17
18
|
onAfterInsert: "onAfterInsert",
|
|
18
19
|
onAfterUpdateQuery: "onAfterUpdateQuery",
|
|
@@ -20,6 +21,7 @@ const HOOK_FUNCTIONS = {
|
|
|
20
21
|
onAfterDeleteQuery: "onAfterDeleteQuery",
|
|
21
22
|
onAfterDelete: "onAfterDelete",
|
|
22
23
|
onAfterPaginate: "onAfterPaginate",
|
|
24
|
+
onAfterAll: "onAfterAll",
|
|
23
25
|
onAfterShow: "onAfterShow",
|
|
24
26
|
};
|
|
25
27
|
|
|
@@ -52,7 +54,8 @@ const HANDLERS = {
|
|
|
52
54
|
SHOW: "show",
|
|
53
55
|
UPDATE: "update",
|
|
54
56
|
DELETE: "destroy",
|
|
55
|
-
|
|
57
|
+
PATCH: "patch",
|
|
58
|
+
ALL: "all",
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
const DEFAULT_HANDLERS = [
|
|
@@ -60,6 +63,7 @@ const DEFAULT_HANDLERS = [
|
|
|
60
63
|
HANDLERS.PAGINATE,
|
|
61
64
|
HANDLERS.SHOW,
|
|
62
65
|
HANDLERS.UPDATE,
|
|
66
|
+
HANDLERS.PATCH,
|
|
63
67
|
HANDLERS.DELETE,
|
|
64
68
|
];
|
|
65
69
|
|
|
@@ -68,35 +72,41 @@ const HTTP_METHODS = {
|
|
|
68
72
|
GET: "GET",
|
|
69
73
|
PUT: "PUT",
|
|
70
74
|
DELETE: "DELETE",
|
|
75
|
+
PATCH: "PATCH",
|
|
71
76
|
};
|
|
72
77
|
|
|
73
78
|
const API_ROUTE_TEMPLATES = {
|
|
74
79
|
[HANDLERS.INSERT]: {
|
|
75
|
-
url: (parentUrl, resource) =>
|
|
80
|
+
url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
|
|
76
81
|
method: HTTP_METHODS.POST,
|
|
77
82
|
},
|
|
78
83
|
[HANDLERS.PAGINATE]: {
|
|
79
|
-
url: (parentUrl, resource) =>
|
|
84
|
+
url: (prefix, parentUrl, resource) => `/${prefix}/${parentUrl}${resource}`,
|
|
85
|
+
method: HTTP_METHODS.GET,
|
|
86
|
+
},
|
|
87
|
+
[HANDLERS.ALL]: {
|
|
88
|
+
url: (prefix, parentUrl, resource) =>
|
|
89
|
+
`/${prefix}/${parentUrl}${resource}/all`,
|
|
80
90
|
method: HTTP_METHODS.GET,
|
|
81
91
|
},
|
|
82
92
|
[HANDLERS.SHOW]: {
|
|
83
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
84
|
-
|
|
93
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
94
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
85
95
|
method: HTTP_METHODS.GET,
|
|
86
96
|
},
|
|
87
97
|
[HANDLERS.UPDATE]: {
|
|
88
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
89
|
-
|
|
98
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
99
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
90
100
|
method: HTTP_METHODS.PUT,
|
|
91
101
|
},
|
|
92
|
-
[HANDLERS.
|
|
93
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
94
|
-
|
|
95
|
-
method: HTTP_METHODS.
|
|
102
|
+
[HANDLERS.PATCH]: {
|
|
103
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
104
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
105
|
+
method: HTTP_METHODS.PATCH,
|
|
96
106
|
},
|
|
97
107
|
[HANDLERS.DELETE]: {
|
|
98
|
-
url: (parentUrl, resource, primaryKey) =>
|
|
99
|
-
|
|
108
|
+
url: (prefix, parentUrl, resource, primaryKey) =>
|
|
109
|
+
`/${prefix}/${parentUrl}${resource}/:${primaryKey}`,
|
|
100
110
|
method: HTTP_METHODS.DELETE,
|
|
101
111
|
},
|
|
102
112
|
};
|
package/src/core/Model.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import pluralize from "pluralize";
|
|
2
2
|
import { snakeCase } from "snake-case";
|
|
3
|
-
import { RELATIONSHIPS,
|
|
4
|
-
const { INSERT, SHOW, UPDATE, PAGINATE, DELETE } = HANDLERS;
|
|
3
|
+
import { RELATIONSHIPS, DEFAULT_HANDLERS } from "./../constants.js";
|
|
5
4
|
|
|
6
5
|
class Model {
|
|
7
6
|
constructor() {
|
|
@@ -25,7 +24,7 @@ class Model {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
get handlers() {
|
|
28
|
-
return [
|
|
27
|
+
return [...DEFAULT_HANDLERS];
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
get middlewares() {
|
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
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callHooks,
|
|
3
|
+
getRelatedData,
|
|
4
|
+
filterHiddenFields,
|
|
5
|
+
serializeData,
|
|
6
|
+
addForeignKeyQuery,
|
|
7
|
+
} from "./helpers.js";
|
|
8
|
+
import { HOOK_FUNCTIONS, HANDLERS } from "./../constants.js";
|
|
9
|
+
import QueryParser from "./../core/QueryParser.js";
|
|
10
|
+
|
|
11
|
+
export default async (context) => {
|
|
12
|
+
const { request, response, model, models, trx, relation, parentModel } =
|
|
13
|
+
context;
|
|
14
|
+
|
|
15
|
+
const queryParser = new QueryParser({ model, models });
|
|
16
|
+
|
|
17
|
+
// We should parse URL query string to use as condition in Lucid query
|
|
18
|
+
const conditions = queryParser.get(request.query);
|
|
19
|
+
|
|
20
|
+
// Creating a new database query
|
|
21
|
+
const query = trx.from(model.instance.table);
|
|
22
|
+
|
|
23
|
+
// Users should be able to select some fields to show.
|
|
24
|
+
queryParser.applyFields(query, conditions.fields);
|
|
25
|
+
|
|
26
|
+
// Binding parent id if there is.
|
|
27
|
+
addForeignKeyQuery(request, query, relation, parentModel);
|
|
28
|
+
|
|
29
|
+
// Users should be able to filter records
|
|
30
|
+
queryParser.applyWheres(query, conditions.q);
|
|
31
|
+
|
|
32
|
+
await callHooks(model, HOOK_FUNCTIONS.onBeforeAll, {
|
|
33
|
+
...context,
|
|
34
|
+
conditions,
|
|
35
|
+
query,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// User should be able to select sorting fields and types
|
|
39
|
+
queryParser.applySorting(query, conditions.sort);
|
|
40
|
+
|
|
41
|
+
const result = await query;
|
|
42
|
+
|
|
43
|
+
// We should try to get related data if there is any
|
|
44
|
+
await getRelatedData(
|
|
45
|
+
result.data,
|
|
46
|
+
conditions.with,
|
|
47
|
+
model,
|
|
48
|
+
models,
|
|
49
|
+
trx,
|
|
50
|
+
HANDLERS.ALL,
|
|
51
|
+
request
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await callHooks(model, HOOK_FUNCTIONS.onAfterAll, {
|
|
55
|
+
...context,
|
|
56
|
+
result,
|
|
57
|
+
conditions,
|
|
58
|
+
query,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Serializing the data by the model's serialize method
|
|
62
|
+
result.data = await serializeData(
|
|
63
|
+
result.data,
|
|
64
|
+
model.instance.serialize,
|
|
65
|
+
HANDLERS.ALL,
|
|
66
|
+
request
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Filtering hidden fields from the response data.
|
|
70
|
+
filterHiddenFields(result.data, model.instance.hiddens);
|
|
71
|
+
|
|
72
|
+
return response.json(result);
|
|
73
|
+
};
|
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/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import all from "./all.js";
|
|
2
|
+
import patch from "./patch.js";
|
|
2
3
|
import store from "./store.js";
|
|
3
4
|
import show from "./show.js";
|
|
4
5
|
import paginate from "./paginate.js";
|
|
5
6
|
import update from "./update.js";
|
|
6
7
|
import destroy from "./destroy.js";
|
|
7
8
|
|
|
8
|
-
export default {
|
|
9
|
+
export default { all, patch, store, show, paginate, update, destroy };
|
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);
|
|
@@ -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 = await serializeData(
|
|
84
|
+
item = await serializeData(
|
|
85
|
+
item,
|
|
86
|
+
model.instance.serialize,
|
|
87
|
+
HANDLERS.PATCH,
|
|
88
|
+
request
|
|
89
|
+
);
|
|
82
90
|
|
|
83
91
|
// Filtering hidden fields from the response data.
|
|
84
92
|
filterHiddenFields([item], 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
|
@@ -43,9 +43,14 @@ export default async (context) => {
|
|
|
43
43
|
formData,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
const [returningResult] = await trx(model.instance.table)
|
|
47
47
|
.insert(formData)
|
|
48
|
-
.returning(
|
|
48
|
+
.returning(model.instance.primaryKey);
|
|
49
|
+
|
|
50
|
+
let insertedPrimaryKeyValue =
|
|
51
|
+
typeof returningResult === "number"
|
|
52
|
+
? returningResult
|
|
53
|
+
: returningResult[model.instance.primaryKey];
|
|
49
54
|
|
|
50
55
|
// If the user use a special primary key value, we should use that value
|
|
51
56
|
if (insertedPrimaryKeyValue === 0) {
|
|
@@ -63,7 +68,12 @@ export default async (context) => {
|
|
|
63
68
|
});
|
|
64
69
|
|
|
65
70
|
// Serializing the data by the model's serialize method
|
|
66
|
-
item = await serializeData(
|
|
71
|
+
item = await serializeData(
|
|
72
|
+
item,
|
|
73
|
+
model.instance.serialize,
|
|
74
|
+
HANDLERS.INSERT,
|
|
75
|
+
request
|
|
76
|
+
);
|
|
67
77
|
|
|
68
78
|
// Filtering hidden fields from the response data.
|
|
69
79
|
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,6 +1,6 @@
|
|
|
1
1
|
import IoC from "./../core/IoC.js";
|
|
2
2
|
import pluralize from "pluralize";
|
|
3
|
-
import { paramCase } from "change-case";
|
|
3
|
+
import { paramCase, camelCase } from "change-case";
|
|
4
4
|
import { RELATIONSHIPS, API_ROUTE_TEMPLATES } from "./../constants.js";
|
|
5
5
|
import Handlers from "./../handlers/index.js";
|
|
6
6
|
|
|
@@ -54,6 +54,10 @@ const requestHandler = async (handler, req, res, next, context) => {
|
|
|
54
54
|
await context.trx.rollback();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
if (error.type === "HttpResponse") {
|
|
58
|
+
return res.status(error.status).json(error.content);
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
next(error);
|
|
58
62
|
}
|
|
59
63
|
};
|
|
@@ -118,7 +122,7 @@ const createNestedRoutes = async (
|
|
|
118
122
|
await createRouteByModel(
|
|
119
123
|
model,
|
|
120
124
|
models,
|
|
121
|
-
`${urlPrefix}${resource}/:${
|
|
125
|
+
`${urlPrefix}${resource}/:${camelCase(relation.foreignKey)}/`,
|
|
122
126
|
model,
|
|
123
127
|
relation,
|
|
124
128
|
false
|
|
@@ -160,6 +164,21 @@ const getModelMiddlewares = (model, handler) => {
|
|
|
160
164
|
return middlewares;
|
|
161
165
|
};
|
|
162
166
|
|
|
167
|
+
export const getRootPrefix = async () => {
|
|
168
|
+
const config = await IoC.use("Config");
|
|
169
|
+
let prefix = config?.Application?.prefix || "api";
|
|
170
|
+
|
|
171
|
+
if (prefix.substr(0, 1) === "/") {
|
|
172
|
+
prefix = prefix.substr(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (prefix.substr(prefix.length - 1) === "/") {
|
|
176
|
+
prefix = prefix.substr(0, prefix.length - 1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return prefix;
|
|
180
|
+
};
|
|
181
|
+
|
|
163
182
|
const createRouteByModel = async (
|
|
164
183
|
model,
|
|
165
184
|
models,
|
|
@@ -197,6 +216,7 @@ const createRouteByModel = async (
|
|
|
197
216
|
|
|
198
217
|
const routeTemplate = API_ROUTE_TEMPLATES[handler];
|
|
199
218
|
const url = routeTemplate.url(
|
|
219
|
+
await getRootPrefix(),
|
|
200
220
|
urlPrefix,
|
|
201
221
|
resource,
|
|
202
222
|
model.instance.primaryKey
|