@hitchy/plugin-odem-rest 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* (c) 2020 cepharum GmbH, Berlin, http://cepharum.de
|
|
3
|
-
*
|
|
4
|
-
* The MIT License (MIT)
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) 2020 cepharum GmbH
|
|
7
|
-
*
|
|
8
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
-
* in the Software without restriction, including without limitation the rights
|
|
11
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
-
* furnished to do so, subject to the following conditions:
|
|
14
|
-
*
|
|
15
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
-
* copies or substantial portions of the Software.
|
|
17
|
-
*
|
|
18
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
-
* SOFTWARE.
|
|
25
|
-
*
|
|
26
|
-
* @author: cepharum
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
1
|
"use strict";
|
|
30
2
|
|
|
31
3
|
module.exports = function() {
|
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* (c) 2020 cepharum GmbH, Berlin, http://cepharum.de
|
|
3
|
-
*
|
|
4
|
-
* The MIT License (MIT)
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) 2020 cepharum GmbH
|
|
7
|
-
*
|
|
8
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
-
* in the Software without restriction, including without limitation the rights
|
|
11
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
-
* furnished to do so, subject to the following conditions:
|
|
14
|
-
*
|
|
15
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
-
* copies or substantial portions of the Software.
|
|
17
|
-
*
|
|
18
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
-
* SOFTWARE.
|
|
25
|
-
*
|
|
26
|
-
* @author: cepharum
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
1
|
"use strict";
|
|
30
2
|
|
|
31
3
|
const PtnAcceptsValue = /^\s*(?:function\s*(?:\s\S+\s*)?)?\(\s*[^\s)]/;
|
package/index.js
CHANGED
|
@@ -156,6 +156,7 @@ module.exports = function() {
|
|
|
156
156
|
|
|
157
157
|
// here comes the REST-compliant part
|
|
158
158
|
routes.set( "GET " + resolve( modelUrl ), reqBadModel || reqFetchItems );
|
|
159
|
+
routes.set( "QUERY " + resolve( modelUrl ), reqBadModel || reqFetchItems );
|
|
159
160
|
routes.set( "GET " + resolve( modelUrl, ":uuid" ), reqBadModel || reqFetchItem );
|
|
160
161
|
|
|
161
162
|
routes.set( "HEAD " + resolve( modelUrl, ":uuid" ), reqBadModel || reqCheckItem );
|
|
@@ -314,7 +315,13 @@ module.exports = function() {
|
|
|
314
315
|
return undefined;
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
|
|
318
|
+
let handler = reqListAll;
|
|
319
|
+
|
|
320
|
+
if ( req.query.q || req.query.query || req.method === "QUERY" ) {
|
|
321
|
+
handler = reqListMatches;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return handler.call( this, req, res );
|
|
318
325
|
}
|
|
319
326
|
|
|
320
327
|
/**
|
|
@@ -356,30 +363,66 @@ module.exports = function() {
|
|
|
356
363
|
* @param {Hitchy.Core.ServerResponse} res API for creating response
|
|
357
364
|
* @returns {Promise|undefined} promises request processed successfully
|
|
358
365
|
*/
|
|
359
|
-
function reqListMatches( req, res ) {
|
|
366
|
+
async function reqListMatches( req, res ) {
|
|
360
367
|
logDebug( "got request listing matching items" );
|
|
361
368
|
|
|
362
369
|
if ( !Schema.mayBeExposed( req, Model ) ) {
|
|
363
370
|
res.status( 403 ).json( { error: "access forbidden by model" } );
|
|
364
|
-
return
|
|
371
|
+
return;
|
|
365
372
|
}
|
|
366
373
|
|
|
367
|
-
const {
|
|
368
|
-
|
|
369
|
-
|
|
374
|
+
const {
|
|
375
|
+
q: simpleQuery = "",
|
|
376
|
+
offset = 0,
|
|
377
|
+
limit = Infinity,
|
|
378
|
+
sortBy = null,
|
|
379
|
+
descending = false,
|
|
380
|
+
loadRecords = true,
|
|
381
|
+
count = false
|
|
382
|
+
} = req.query;
|
|
383
|
+
let parsedQuery;
|
|
384
|
+
|
|
385
|
+
if ( simpleQuery ) {
|
|
386
|
+
// support old-style simple queries with ?q=<query>
|
|
387
|
+
parsedQuery = parseQuery( simpleQuery );
|
|
388
|
+
|
|
389
|
+
if ( !parsedQuery ) {
|
|
390
|
+
res.status( 400 ).json( { error: "invalid query, e.g. use ?q=name:operation:value" } );
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
} else if ( req.query.query ) {
|
|
394
|
+
// support JSON-encoded queries in URL with ?query=<json-query>
|
|
395
|
+
try {
|
|
396
|
+
parsedQuery = JSON.parse( req.query.query );
|
|
397
|
+
} catch ( cause ) {
|
|
398
|
+
res.status( 400 ).json( { error: 'invalid extended query, e.g. use ?query={"operation":{"name":value}}' } );
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
} else if ( req.method === "QUERY" ) {
|
|
402
|
+
// support HTTP QUERY method (which is in draft state, currently)
|
|
403
|
+
const query = await req.fetchBody();
|
|
404
|
+
|
|
405
|
+
if ( typeof query === "string" ) {
|
|
406
|
+
try {
|
|
407
|
+
parsedQuery = JSON.parse( query );
|
|
408
|
+
} catch ( cause ) {
|
|
409
|
+
res.status( 400 ).json( { error: "invalid query in QUERY request payload" } );
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
} else if ( query && typeof query === "object" && !Array.isArray( query ) ) {
|
|
413
|
+
parsedQuery = query;
|
|
414
|
+
} else {
|
|
415
|
+
res.status( 400 ).json( { error: "invalid query in QUERY request payload" } );
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
370
419
|
res.status( 400 ).json( { error: "missing query" } );
|
|
371
|
-
return
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const parsedQuery = parseQuery( query );
|
|
375
|
-
if ( !parsedQuery ) {
|
|
376
|
-
res.status( 400 ).json( { error: "invalid query, e.g. use ?q=name:operation:value" } );
|
|
377
|
-
return undefined;
|
|
420
|
+
return;
|
|
378
421
|
}
|
|
379
422
|
|
|
380
423
|
const meta = count || req.headers["x-count"] ? {} : null;
|
|
381
424
|
|
|
382
|
-
|
|
425
|
+
await Model.find( parsedQuery, { offset, limit, sortBy, sortAscendingly: !descending }, {
|
|
383
426
|
metaCollector: meta,
|
|
384
427
|
loadRecords
|
|
385
428
|
} )
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitchy/plugin-odem-rest",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "HTTP REST API for Hitchy's ODM",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"homepage": "https://gitlab.com/hitchy/plugin-odem-rest#plugin-odem-rest",
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@hitchy/core": "^0.7.2",
|
|
22
|
-
"@hitchy/plugin-odem": "^0.7.
|
|
22
|
+
"@hitchy/plugin-odem": "^0.7.4"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@hitchy/server-dev-tools": "^0.4.3",
|
|
26
|
-
"c8": "^7.
|
|
27
|
-
"eslint": "^8.
|
|
26
|
+
"c8": "^7.12.0",
|
|
27
|
+
"eslint": "^8.24.0",
|
|
28
28
|
"eslint-config-cepharum": "^1.0.12",
|
|
29
|
-
"eslint-plugin-promise": "^6.0.
|
|
29
|
+
"eslint-plugin-promise": "^6.0.1",
|
|
30
30
|
"mocha": "^10.0.0",
|
|
31
31
|
"should": "^13.2.3",
|
|
32
32
|
"should-http": "^0.1.1"
|
package/{README.md → readme.md}
RENAMED
|
@@ -6,6 +6,9 @@ HTTP REST API for [Hitchy's](https://core.hitchy.org/) [ODM](https://odem.hitchy
|
|
|
6
6
|
|
|
7
7
|
This plugin is defining blueprint routes for accessing data managed in ODM using REST API.
|
|
8
8
|
|
|
9
|
+
## License
|
|
10
|
+
|
|
11
|
+
[MIT](LICENSE)
|
|
9
12
|
|
|
10
13
|
## Installation
|
|
11
14
|
|
|
@@ -59,7 +62,7 @@ module.exports = {
|
|
|
59
62
|
|
|
60
63
|
When starting your Hitchy-based application it will discover a model named **LocalEmployee** and expose it via REST API using URLs like `/api/local-employee` just because of this package and its dependencies mentioned before.
|
|
61
64
|
|
|
62
|
-
#### Models of Hitchy
|
|
65
|
+
#### Models of Hitchy plugins
|
|
63
66
|
|
|
64
67
|
Due to the way Hitchy is discovering plugins and compiling [components](https://core.hitchy.org/internals/components.html) defined there, this plugin is always covering models defined in installed plugins as well. Thus, any plugin is capable of defining additional models to be supported. In addition, some plugin may extend models defined by another plugin.
|
|
65
68
|
|
|
@@ -90,18 +93,19 @@ In Hitchy's ODM all model instances or items are uniquely addressable via UUIDs.
|
|
|
90
93
|
|
|
91
94
|
The provided routes implement these actions:
|
|
92
95
|
|
|
93
|
-
| Method | URL | Action
|
|
94
|
-
|
|
95
|
-
| GET | `/api/model` | Lists items of selected model.
|
|
96
|
-
| GET | `/api/model/<uuid>` | Fetches properties of selected item.
|
|
97
|
-
| PUT | `/api/model/<uuid>` | Replaces all properties of selected item with those given in request body. Selected item is created when missing.
|
|
98
|
-
| PATCH | `/api/model/<uuid>` | Adjusts selected item by replacing values of properties given in request body (leaving those missing in request body untouched).
|
|
99
|
-
| POST | `/api/model` | Creates new item initialized with properties provided in request body.
|
|
100
|
-
| DELETE | `/api/model/<uuid>` | Removes selected item from model's collection.
|
|
101
|
-
| HEAD | `/api/model` | Tests if selected model exists.
|
|
102
|
-
| HEAD | `/api/model/<uuid>` | Tests if selected item exists.
|
|
96
|
+
| Method | URL | Action |
|
|
97
|
+
|---|---|------------------------------------------------------------------------------------------------------------------------------------------|
|
|
98
|
+
| GET | `/api/model` | Lists items of selected model. |
|
|
99
|
+
| GET | `/api/model/<uuid>` | Fetches properties of selected item. |
|
|
100
|
+
| PUT | `/api/model/<uuid>` | Replaces all properties of selected item with those given in request body. Selected item is created when missing. |
|
|
101
|
+
| PATCH | `/api/model/<uuid>` | Adjusts selected item by replacing values of properties given in request body (leaving those missing in request body untouched). |
|
|
102
|
+
| POST | `/api/model` | Creates new item initialized with properties provided in request body. |
|
|
103
|
+
| DELETE | `/api/model/<uuid>` | Removes selected item from model's collection. |
|
|
104
|
+
| HEAD | `/api/model` | Tests if selected model exists. |
|
|
105
|
+
| HEAD | `/api/model/<uuid>` | Tests if selected item exists. |
|
|
106
|
+
| QUERY | `/api/model` | Fetches items of model matching JSON-encoded query in request body.<br><br>_Not working unless Node.js is supporting [HTTP QUERY method](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1)._ |
|
|
103
107
|
|
|
104
|
-
In addition following URLs are available for accessing schema information:
|
|
108
|
+
In addition, following URLs are available for accessing schema information:
|
|
105
109
|
|
|
106
110
|
| Method | URL | Action |
|
|
107
111
|
|---|---|---|
|
|
@@ -121,7 +125,7 @@ Response status code indicates basic result of either requests.
|
|
|
121
125
|
| 405 | A given method isn't allowed on selected model or item. This is basically a more specific information related to performing some invalid request like trying to PATCH or DELETE a whole model instead of a single item. |
|
|
122
126
|
|
|
123
127
|
|
|
124
|
-
### Convenience
|
|
128
|
+
### Convenience routes
|
|
125
129
|
|
|
126
130
|
By default, the module is exposing another set of routes for every model that enables requesting either supported action using GET-requests. This is assumed to be very useful in development e.g. to conveniently add or remove items using regular browser.
|
|
127
131
|
|
|
@@ -139,7 +143,7 @@ There are no extra routes following this pattern for actions that are exposed vi
|
|
|
139
143
|
|
|
140
144
|
All request data is provided in query parameters instead of request body for GET requests don't have a body.
|
|
141
145
|
|
|
142
|
-
#### Disabling
|
|
146
|
+
#### Disabling feature
|
|
143
147
|
|
|
144
148
|
Disable this feature in the configuration file **config/model.js**:
|
|
145
149
|
|
|
@@ -150,16 +154,16 @@ exports.model = {
|
|
|
150
154
|
```
|
|
151
155
|
|
|
152
156
|
|
|
153
|
-
### Extended
|
|
157
|
+
### Extended fetching of items
|
|
154
158
|
|
|
155
159
|
Whenever fetching a list of items using GET request on a model's URL there are additional options for controlling the retrieved list.
|
|
156
160
|
|
|
157
161
|
|
|
158
162
|
#### Filtering
|
|
159
163
|
|
|
160
|
-
Using query parameter `q` the list of fetched items can be limited to those items matching criteria given in that query
|
|
164
|
+
Using query parameter `q` the list of fetched items can be limited to those items matching criteria given in that simple query. The abbreviated name `q` just refers to a _search query_.
|
|
161
165
|
|
|
162
|
-
##### Simple
|
|
166
|
+
##### Simple comparisons
|
|
163
167
|
|
|
164
168
|
The search query may comply with the pattern `name:operation:value` to compare every item's property with a given value using one of these operations:
|
|
165
169
|
|
|
@@ -174,7 +178,7 @@ The search query may comply with the pattern `name:operation:value` to compare e
|
|
|
174
178
|
|
|
175
179
|
For example, a GET-request for `/api/localEmployee?q=lastName:eq:Doe` will deliver all items of model **LocalEmployee** with property **lastName** equal given value **Doe**. The value may contain further colons.
|
|
176
180
|
|
|
177
|
-
#####
|
|
181
|
+
##### Unary tests
|
|
178
182
|
|
|
179
183
|
Alternatively the search query may comply with the pattern `name:operation` for testing the named property using one of these supported operations:
|
|
180
184
|
|
|
@@ -185,7 +189,7 @@ Alternatively the search query may comply with the pattern `name:operation` for
|
|
|
185
189
|
|
|
186
190
|
For example, a GET-request for `/api/localEmployee?q=lastName:null` will deliver all items of model **LocalEmployee** with unset property **lastName**.
|
|
187
191
|
|
|
188
|
-
#####
|
|
192
|
+
##### Ternary tests
|
|
189
193
|
|
|
190
194
|
A third type of test operations are ternary tests. This refers to operations consisting of three parameters: the property's name and two values instead of one to compare that property's values with. Related queries comply with the pattern `name:operation:value:value`, hence using colon in first given value is not supported.
|
|
191
195
|
|
|
@@ -195,9 +199,26 @@ A third type of test operations are ternary tests. This refers to operations con
|
|
|
195
199
|
|
|
196
200
|
For example, a GET-request for `/api/localEmployee?q=salary:between:2000:4000` will deliver all items of model **LocalEmployee** with value of property **salary** in range from 2000 to 4000.
|
|
197
201
|
|
|
198
|
-
##### Complex
|
|
202
|
+
##### Complex tests <Bade type=info text=v0.5.2+></Badge>
|
|
199
203
|
|
|
200
|
-
|
|
204
|
+
Hitchy ODM supports more complex queries that can't be encoded as such simple queries as described above. Thus, a different way of querying has been added.
|
|
205
|
+
|
|
206
|
+
When using parameter `query` instead of `q`, its value is assumed to be a JSON-encoded query complying with query syntax supported by [Model.find() method of Hitchy ODM](https://odem.hitchy.org/api/model.html#model-find).
|
|
207
|
+
|
|
208
|
+
```http request
|
|
209
|
+
GET /api/user?query={"in":{"name":["john","jane","jason"]}}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
_This example omits proper URL encoding of value to `query` parameter for illustration purposes. You should always encode queries._
|
|
213
|
+
|
|
214
|
+
In addition, support for upcoming [HTTP QUERY method](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/?include_text=1) has been prepared. However, this one does not work unless Node.js is accepting HTTP requests with method `QUERY`.
|
|
215
|
+
|
|
216
|
+
```http request
|
|
217
|
+
QUERY /api/user
|
|
218
|
+
Content-Type: application/json
|
|
219
|
+
|
|
220
|
+
{"in":{"name":["john","jane","jason"]}}
|
|
221
|
+
```
|
|
201
222
|
|
|
202
223
|
|
|
203
224
|
#### Sorting
|