@hitchy/plugin-odem-rest 0.6.0 → 0.7.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  module.exports = function() {
4
4
  const Config = this.config;
5
- const Services = this.runtime.services;
5
+ const Services = this.services;
6
6
 
7
7
  const CommonlyAcceptedHeaders = [
8
8
  "Accept", "Accept-Language", "Authorization", "Content-Language",
package/index.js CHANGED
@@ -4,7 +4,7 @@ const { posix: { resolve } } = require( "path" );
4
4
 
5
5
  module.exports = function() {
6
6
  const api = this;
7
- const { runtime: { services: Services, models: Models }, utility: { case: Case } } = api;
7
+ const { services: Services, models: Models, utility: { case: Case } } = api;
8
8
 
9
9
  const logDebug = api.log( "hitchy:odem:rest:debug" );
10
10
  const logError = api.log( "hitchy:odem:rest:error" );
@@ -23,10 +23,6 @@ module.exports = function() {
23
23
  before.set( `ALL ${resolve( urlPrefix, ".schema" )}`, CORS.getRequestFilterForSchemata() );
24
24
  after.set( `ALL ${resolve( urlPrefix, ".schema" )}`, reqNotSupported );
25
25
 
26
- if ( api.plugins.authentication ) {
27
- before.set( `GET ${resolve( urlPrefix, ".schema" )}`, Services.AuthorizationPolicyGenerator.mayAccess( "@hitchy.odem.schema" ) );
28
- }
29
-
30
26
  for ( let i = 0, numNames = modelNames.length; i < numNames; i++ ) {
31
27
  const name = modelNames[i];
32
28
  const routeName = Case.pascalToKebab( name );
@@ -36,55 +32,6 @@ module.exports = function() {
36
32
  before.set( `ALL ${resolve( urlPrefix, routeName, ".schema" )}`, CORS.getRequestFilterForModelSchema( model ) );
37
33
  before.set( `ALL ${resolve( urlPrefix, routeName, ":uuid" )}`, CORS.getRequestFilterForModelItem( model ) );
38
34
 
39
- if ( api.plugins.authentication ) {
40
- // add policies for additionally checking a requesting user's authorization
41
- const modelUrl = resolve( urlPrefix, routeName );
42
- const policy = action => Services.AuthorizationPolicyGenerator.mayAccess( `@hitchy.odem.model.${name}.${action}` );
43
-
44
- const handlers = {
45
- schema: policy( "schema" ),
46
- create: policy( "create" ),
47
- list: policy( "list" ),
48
- read: policy( "read" ),
49
- write: policy( "write" ),
50
- check: policy( "check" ),
51
- remove: policy( "remove" ),
52
- };
53
-
54
- // convenience routes
55
- before.set( `GET ${resolve( modelUrl, "create" )}`, handlers.create );
56
- before.set( `GET ${resolve( modelUrl, "write", ":uuid" )}`, handlers.write );
57
- before.set( `GET ${resolve( modelUrl, "replace", ":uuid" )}`, handlers.write );
58
- before.set( `GET ${resolve( modelUrl, "has", ":uuid" )}`, handlers.check );
59
- before.set( `GET ${resolve( modelUrl, "remove", ":uuid" )}`, handlers.remove );
60
-
61
- // extended routes
62
- before.set( `GET ${resolve( modelUrl, ".schema" )}`, policy( "schema" ) );
63
-
64
- // REST-compliant routes
65
- before.set( `GET ${modelUrl}`, function( req, res, next ) {
66
- const tail = req.url.substring( modelUrl.length ).trim();
67
-
68
- if ( tail && tail !== "/" ) {
69
- next();
70
- } else {
71
- handlers.list.call( this, req, res, next );
72
- }
73
- } );
74
- before.set( `POST ${resolve( modelUrl )}`, handlers.create );
75
- before.set( `GET ${resolve( modelUrl, ":uuid" )}`, ( req, res, next ) => {
76
- if ( req.params.uuid === ".schema" ) {
77
- next();
78
- } else {
79
- handlers.read.call( this, req, res, next );
80
- }
81
- } );
82
- before.set( `PATCH ${resolve( modelUrl, ":uuid" )}`, handlers.write );
83
- before.set( `PUT ${resolve( modelUrl, ":uuid" )}`, handlers.write );
84
- before.set( `HEAD ${resolve( modelUrl, ":uuid" )}`, handlers.check );
85
- before.set( `DELETE ${resolve( modelUrl, ":uuid" )}`, handlers.remove );
86
- }
87
-
88
35
  after.set( `ALL ${resolve( urlPrefix, routeName )}`, reqNotSupported );
89
36
  }
90
37
 
@@ -123,7 +70,7 @@ module.exports = function() {
123
70
  const routeName = Case.pascalToKebab( name );
124
71
  const model = Models[name] || {};
125
72
 
126
- addRoutesOnModel( routes, urlPrefix, routeName, model, convenience );
73
+ addRoutesOnModel( routes, urlPrefix, routeName, name, model, convenience );
127
74
  }
128
75
 
129
76
  routes.set( "HEAD " + resolve( urlPrefix, ":model" ), ( _, res ) => res.status( 404 ).send() );
@@ -133,6 +80,20 @@ module.exports = function() {
133
80
  },
134
81
  };
135
82
 
83
+ /**
84
+ * Generates JSON response indicating rejection of access.
85
+ *
86
+ * @param {Hitchy.Core.ServerResponse} res response manager
87
+ * @returns {void}
88
+ */
89
+ function resAccessForbidden( res ) {
90
+ res
91
+ .status( 403 )
92
+ .json( {
93
+ error: "access forbidden",
94
+ } );
95
+ }
96
+
136
97
  /**
137
98
  * Adds routes handling common requests not related to particular model.
138
99
  *
@@ -143,7 +104,7 @@ module.exports = function() {
143
104
  * @returns {void}
144
105
  */
145
106
  function addGlobalRoutes( routes, urlPrefix, models ) {
146
- const { runtime: { services: { Model: BaseModel, OdemSchema } }, utility: { case: { pascalToKebab } } } = api;
107
+ const { services: { Model: BaseModel, OdemSchema }, utility: { case: { pascalToKebab } } } = api;
147
108
 
148
109
  routes.set( `GET ${resolve( urlPrefix, ".schema" )}`, reqFetchSchemata );
149
110
 
@@ -154,7 +115,12 @@ module.exports = function() {
154
115
  * @param {Hitchy.Core.ServerResponse} res response manager
155
116
  * @returns {void}
156
117
  */
157
- function reqFetchSchemata( req, res ) {
118
+ async function reqFetchSchemata( req, res ) {
119
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, "@hitchy.odem.schema" ) ) {
120
+ resAccessForbidden( res );
121
+ return;
122
+ }
123
+
158
124
  const modelKeys = Object.keys( models );
159
125
  const numModels = modelKeys.length;
160
126
 
@@ -183,11 +149,12 @@ module.exports = function() {
183
149
  * route patterns into function handling requests matching that pattern
184
150
  * @param {string} urlPrefix common prefix to use on every route regarding any model-related processing
185
151
  * @param {string} routeName name of model to be used in path name of request
152
+ * @param {string} modelName name of model derived from name extracted from path name of request in `routeName`
186
153
  * @param {class<Model>} Model model class
187
154
  * @param {boolean} includeConvenienceRoutes set true to include additional set of routes for controlling all action via GET-requests
188
155
  * @returns {void}
189
156
  */
190
- function addRoutesOnModel( routes, urlPrefix, routeName, Model, includeConvenienceRoutes ) {
157
+ function addRoutesOnModel( routes, urlPrefix, routeName, modelName, Model, includeConvenienceRoutes ) {
191
158
  const { Model: BaseModel, OdemSchema: Schema, OdemUtilityUuid: { ptnUuid } } = Services;
192
159
 
193
160
  const modelUrl = resolve( urlPrefix, routeName );
@@ -262,9 +229,14 @@ module.exports = function() {
262
229
  * @param {Hitchy.Core.ServerResponse} res API for creating response
263
230
  * @returns {void}
264
231
  */
265
- function reqFetchSchema( req, res ) {
232
+ async function reqFetchSchema( req, res ) {
266
233
  logDebug( "got request fetching schema" );
267
234
 
235
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.schema` ) ) {
236
+ resAccessForbidden( res );
237
+ return;
238
+ }
239
+
268
240
  if ( !Services.OdemSchema.mayBeExposed( req, Model ) ) {
269
241
  res.status( 403 ).json( { error: "access forbidden by model" } );
270
242
  return;
@@ -281,23 +253,28 @@ module.exports = function() {
281
253
  * @param {Hitchy.Core.ServerResponse} res API for creating response
282
254
  * @returns {Promise|undefined} promises request processed successfully
283
255
  */
284
- function reqCheckItem( req, res ) {
256
+ async function reqCheckItem( req, res ) {
285
257
  logDebug( "got request checking if some item exists" );
286
258
 
259
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.check` ) ) {
260
+ resAccessForbidden( res );
261
+ return;
262
+ }
263
+
287
264
  if ( !Schema.mayBeExposed( req, Model ) ) {
288
265
  res.status( 403 ).json( { error: "access forbidden by model" } );
289
- return undefined;
266
+ return;
290
267
  }
291
268
 
292
269
  const { uuid } = req.params;
293
270
  if ( !ptnUuid.test( uuid ) ) {
294
271
  res.status( 400 ).send();
295
- return undefined;
272
+ return;
296
273
  }
297
274
 
298
275
  const item = new Model( uuid ); // eslint-disable-line new-cap
299
276
 
300
- return item.$exists
277
+ await item.$exists
301
278
  .then( exists => {
302
279
  res.status( exists ? 200 : 404 ).send();
303
280
  } )
@@ -312,25 +289,30 @@ module.exports = function() {
312
289
  *
313
290
  * @param {Hitchy.Core.IncomingMessage} req description of request
314
291
  * @param {Hitchy.Core.ServerResponse} res API for creating response
315
- * @returns {Promise} promises request processed successfully
292
+ * @returns {Promise<void>} promises request processed successfully
316
293
  */
317
- function reqFetchItem( req, res ) {
294
+ async function reqFetchItem( req, res ) {
318
295
  logDebug( "got request fetching some item" );
319
296
 
297
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.read` ) ) {
298
+ resAccessForbidden( res );
299
+ return;
300
+ }
301
+
320
302
  if ( !Schema.mayBeExposed( req, Model ) ) {
321
303
  res.status( 403 ).json( { error: "access forbidden by model" } );
322
- return undefined;
304
+ return;
323
305
  }
324
306
 
325
307
  const { uuid } = req.params;
326
308
  if ( !ptnUuid.test( uuid ) ) {
327
309
  res.status( 400 ).json( { error: "invalid UUID" } );
328
- return undefined;
310
+ return;
329
311
  }
330
312
 
331
313
  const item = new Model( uuid ); // eslint-disable-line new-cap
332
314
 
333
- return item.load()
315
+ await item.load()
334
316
  .then( loaded => res.json( Schema.filterItem( loaded.toObject( { serialized: true } ), req, Model, "read" ) ) )
335
317
  .catch( error => {
336
318
  logError( "fetching %s:", routeName, error );
@@ -340,6 +322,7 @@ module.exports = function() {
340
322
  res.status( 404 ).json( { error: "selected item not found" } );
341
323
  break;
342
324
  }
325
+
343
326
  default : {
344
327
  res.status( 500 ).json( { error: error.message } );
345
328
  }
@@ -355,17 +338,22 @@ module.exports = function() {
355
338
  * @param {Hitchy.Core.ServerResponse} res response controller
356
339
  * @returns {Promise} promises response sent
357
340
  */
358
- function reqFetchItems( req, res ) {
341
+ async function reqFetchItems( req, res ) {
359
342
  logDebug( "got request fetching items" );
360
343
 
344
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.list` ) ) {
345
+ resAccessForbidden( res );
346
+ return;
347
+ }
348
+
361
349
  if ( !Schema.mayBeExposed( req, Model ) ) {
362
350
  res.status( 403 ).json( { error: "access forbidden by model" } );
363
- return undefined;
351
+ return;
364
352
  }
365
353
 
366
354
  if ( req.headers["x-list-as-array"] ) {
367
355
  res.status( 400 ).json( { error: "fetching items as array is deprecated for security reasons" } );
368
- return undefined;
356
+ return;
369
357
  }
370
358
 
371
359
  let handler = reqListAll;
@@ -374,7 +362,7 @@ module.exports = function() {
374
362
  handler = reqListMatches;
375
363
  }
376
364
 
377
- return handler.call( this, req, res );
365
+ await handler.call( this, req, res );
378
366
  }
379
367
 
380
368
  /**
@@ -505,17 +493,18 @@ module.exports = function() {
505
493
  * @param {Hitchy.Core.ServerResponse} res API for creating response
506
494
  * @returns {Promise|undefined} promises request processed successfully
507
495
  */
508
- function reqListAll( req, res ) {
496
+ async function reqListAll( req, res ) {
509
497
  logDebug( "got request listing all items" );
510
498
 
511
499
  if ( !Schema.mayBeExposed( req, Model ) ) {
512
500
  res.status( 403 ).json( { error: "access forbidden by model" } );
513
- return undefined;
501
+ return;
514
502
  }
515
503
 
516
504
  const { offset = 0, limit = Infinity, sortBy = null, descending = false, loadRecords = true, count = false } = req.query;
517
505
  const meta = count || req.headers["x-count"] ? {} : null;
518
- return Model.list( {
506
+
507
+ await Model.list( {
519
508
  offset,
520
509
  limit,
521
510
  sortBy,
@@ -547,17 +536,22 @@ module.exports = function() {
547
536
  * @param {Hitchy.Core.ServerResponse} res API for creating response
548
537
  * @returns {Promise|undefined} promises request processed successfully
549
538
  */
550
- function reqCreateItem( req, res ) {
539
+ async function reqCreateItem( req, res ) {
551
540
  logDebug( "got request creating item" );
552
541
 
542
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.create` ) ) {
543
+ resAccessForbidden( res );
544
+ return;
545
+ }
546
+
553
547
  if ( !Schema.mayBeExposed( req, Model ) ) {
554
548
  res.status( 403 ).json( { error: "access forbidden by model" } );
555
- return undefined;
549
+ return;
556
550
  }
557
551
 
558
552
  const item = new Model(); // eslint-disable-line new-cap
559
553
 
560
- return ( req.method === "GET" ? Promise.resolve( req.query ) : req.fetchBody() )
554
+ await ( req.method === "GET" ? Promise.resolve( req.query ) : req.fetchBody() )
561
555
  .then( record => {
562
556
  if ( record ) {
563
557
  if ( record.uuid ) {
@@ -595,23 +589,28 @@ module.exports = function() {
595
589
  * @param {Hitchy.Core.ServerResponse} res API for creating response
596
590
  * @returns {Promise|undefined} promises request processed successfully
597
591
  */
598
- function reqModifyItem( req, res ) {
592
+ async function reqModifyItem( req, res ) {
599
593
  logDebug( "got request to modify some item" );
600
594
 
595
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.write` ) ) {
596
+ resAccessForbidden( res );
597
+ return;
598
+ }
599
+
601
600
  if ( !Schema.mayBeExposed( req, Model ) ) {
602
601
  res.status( 403 ).json( { error: "access forbidden by model" } );
603
- return undefined;
602
+ return;
604
603
  }
605
604
 
606
605
  const { uuid } = req.params;
607
606
  if ( !ptnUuid.test( uuid ) ) {
608
607
  res.status( 400 ).json( { error: "invalid UUID" } );
609
- return undefined;
608
+ return;
610
609
  }
611
610
 
612
611
  const item = new Model( uuid ); // eslint-disable-line new-cap
613
612
 
614
- return item.$exists
613
+ await item.$exists
615
614
  .then( exists => {
616
615
  if ( !exists ) {
617
616
  res.status( 404 ).json( { error: "selected item not found" } );
@@ -655,23 +654,28 @@ module.exports = function() {
655
654
  * @param {Hitchy.Core.ServerResponse} res API for creating response
656
655
  * @returns {Promise|undefined} promises request processed successfully
657
656
  */
658
- function reqReplaceItem( req, res ) {
657
+ async function reqReplaceItem( req, res ) {
659
658
  logDebug( "got request replacing some item" );
660
659
 
660
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.write` ) ) {
661
+ resAccessForbidden( res );
662
+ return;
663
+ }
664
+
661
665
  if ( !Schema.mayBeExposed( req, Model ) ) {
662
666
  res.status( 403 ).json( { error: "access forbidden by model" } );
663
- return undefined;
667
+ return;
664
668
  }
665
669
 
666
670
  const { uuid } = req.params;
667
671
  if ( !ptnUuid.test( uuid ) ) {
668
672
  res.status( 400 ).json( { error: "invalid UUID" } );
669
- return undefined;
673
+ return;
670
674
  }
671
675
 
672
676
  const item = new Model( uuid, { onUnsaved: false } ); // eslint-disable-line new-cap
673
677
 
674
- return Promise.all( [ item.$exists, req.method === "GET" ? Promise.resolve( req.query ) : req.fetchBody() ] )
678
+ await Promise.all( [ item.$exists, req.method === "GET" ? Promise.resolve( req.query ) : req.fetchBody() ] )
675
679
  .then( ( [ exists, record ] ) => {
676
680
  return ( exists ? item.load() : Promise.resolve() ).then( () => {
677
681
  const propNames = Object.keys( Model.schema.props );
@@ -714,23 +718,28 @@ module.exports = function() {
714
718
  * @param {Hitchy.Core.ServerResponse} res API for creating response
715
719
  * @returns {Promise|undefined} promises request processed successfully
716
720
  */
717
- function reqRemoveItem( req, res ) {
721
+ async function reqRemoveItem( req, res ) {
718
722
  logDebug( "got request removing some item" );
719
723
 
724
+ if ( api.plugins.authentication && !await Services.Authorization.mayAccess( req.user, `@hitchy.odem.model.${modelName}.remove` ) ) {
725
+ resAccessForbidden( res );
726
+ return;
727
+ }
728
+
720
729
  if ( !Schema.mayBeExposed( req, Model ) ) {
721
730
  res.status( 403 ).json( { error: "access forbidden by model" } );
722
- return undefined;
731
+ return;
723
732
  }
724
733
 
725
734
  const { uuid } = req.params;
726
735
  if ( !ptnUuid.test( uuid ) ) {
727
736
  res.status( 400 ).json( { error: "invalid UUID" } );
728
- return undefined;
737
+ return;
729
738
  }
730
739
 
731
740
  const item = new Model( uuid ); // eslint-disable-line new-cap
732
741
 
733
- return item.$exists
742
+ await item.$exists
734
743
  .then( exists => {
735
744
  if ( exists ) {
736
745
  return item.remove()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hitchy/plugin-odem-rest",
3
- "version": "0.6.0",
4
- "description": "HTTP REST API for Hitchy's ODM",
3
+ "version": "0.7.0",
4
+ "description": "HTTP REST API for Hitchy's document-oriented database",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "lint": "eslint .",
@@ -9,8 +9,7 @@
9
9
  },
10
10
  "repository": "https://gitlab.com/hitchy/plugin-odem-rest.git",
11
11
  "keywords": [
12
- "hitchy",
13
- "ODM"
12
+ "hitchy"
14
13
  ],
15
14
  "author": "Thomas Urban",
16
15
  "license": "MIT",
@@ -18,7 +17,7 @@
18
17
  "homepage": "https://gitlab.com/hitchy/plugin-odem-rest#plugin-odem-rest",
19
18
  "peerDependencies": {
20
19
  "@hitchy/core": "0.8.x",
21
- "@hitchy/plugin-odem": "0.8.x",
20
+ "@hitchy/plugin-odem": "0.9.x",
22
21
  "@hitchy/plugin-auth": "0.4.x"
23
22
  },
24
23
  "devDependencies": {
@@ -27,8 +26,8 @@
27
26
  "c8": "^10.1.2",
28
27
  "eslint": "^8.57.0",
29
28
  "eslint-config-cepharum": "^1.0.14",
30
- "eslint-plugin-promise": "^6.1.1",
31
- "mocha": "^10.7.0",
29
+ "eslint-plugin-promise": "^6.6.0",
30
+ "mocha": "^10.7.3",
32
31
  "should": "^13.2.3",
33
32
  "should-http": "^0.1.1"
34
33
  }
package/readme.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # plugin-odem-rest [![pipeline status](https://gitlab.com/hitchy/plugin-odem-rest/badges/master/pipeline.svg)](https://gitlab.com/hitchy/plugin-odem-rest/-/commits/master)
2
2
 
3
- HTTP REST API for [Hitchy's](https://core.hitchy.org/) [ODM](https://odem.hitchy.org/)
3
+ HTTP REST API for [Hitchy's](https://core.hitchy.org/) [document-oriented database](https://odem.hitchy.org/)
4
4
 
5
- [Hitchy](http://core.hitchy.org/) is a server-side framework for developing web applications with [Node.js](https://nodejs.org/). [Odem](https://www.npmjs.com/package/@hitchy/plugin-odem) is plugin for Hitchy implementing an object document management (ODM) using data backends like regular file systems, LevelDBs and temporary in-memory databases.
5
+ [Hitchy](http://core.hitchy.org/) is a server-side framework for developing web applications with [Node.js](https://nodejs.org/). [Odem](https://www.npmjs.com/package/@hitchy/plugin-odem) is a plugin for Hitchy implementing a document-oriented database using data backends like regular file systems, LevelDBs and temporary in-memory databases.
6
6
 
7
- This plugin is defining blueprint routes for accessing data managed in ODM using REST API.
7
+ This plugin is defining blueprint routes for accessing data managed in document-oriented database using REST API.
8
8
 
9
9
  ## License
10
10
 
@@ -44,7 +44,7 @@ exports.auth = {
44
44
 
45
45
  > **Do not use this in a production setup!**
46
46
  >
47
- > This example is granting permissions to create, adjust and remove items of your ODM without any authentication. In addition, all _protected_ models and properties get exposed to everyone.
47
+ > This example is granting permissions to create, adjust and remove items of your document-oriented database without any authentication. In addition, all _protected_ models and properties get exposed to everyone.
48
48
  >
49
49
  > See the [section on authorization](#authorization) for additional information!
50
50
 
@@ -105,7 +105,7 @@ exports.model = {
105
105
 
106
106
  The model's segment in URL `<model>` is derived as the kebab-case version of model's name which is given in PascalCase. Thus, a model definition in a file named **api/models/my-fancy-model.js** is assumed to describe a model named **MyFancyModel** by default, resulting in model's URL segment to be **my-fancy-model** again. So the URL path for the collection of items is `/api/my-fancy-model`.
107
107
 
108
- In Hitchy's ODM all model instances or items are uniquely addressable via UUIDs. By appending an item's UUID to the given URL path of a collection you get the URL path of that item, e.g. `/api/my-fancy-model/01234567-1234-1234-1234-56789abcdef0`.
108
+ In Hitchy's document-oriented database all model instances or items are uniquely addressable via UUIDs. By appending an item's UUID to the given URL path of a collection you get the URL path of that item, e.g. `/api/my-fancy-model/01234567-1234-1234-1234-56789abcdef0`.
109
109
 
110
110
 
111
111
  ### The REST API
@@ -220,9 +220,9 @@ For example, a GET-request for `/api/localEmployee?q=salary:between:2000:4000` w
220
220
 
221
221
  ##### Complex tests <Bade type=info text=v0.5.2+></Badge>
222
222
 
223
- 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.
223
+ Hitchy's document-oriented database 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.
224
224
 
225
- 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).
225
+ 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's document-oriented database](https://odem.hitchy.org/api/model.html#model-find).
226
226
 
227
227
  ```http request
228
228
  GET /api/user?query={"in":{"name":["john","jane","jason"]}}
@@ -272,7 +272,7 @@ Resources are declared to control authorizations for basically interacting with
272
272
  * When creating a new item, accessing the resource `@hitchy.odem.model.<ModelName>.create` must be granted.
273
273
  * When removing an item, accessing the resource `@hitchy.odem.model.<ModelName>.remove` must be granted.
274
274
  * Accessing a model's schema requires the resource `@hitchy.odem.model.<ModelName>.schema` to be granted.
275
- * Accessing the collection of all models' schemata requires the resource `@hitchy.odem.schema` to be granted. This implicitly causes models with their [promote option](https://odem.hitchy.org/guides/defining-models.html#options) being `protected` to be exposed in responses to that request unless a more specific resource `@hitchy.odem.schema.model.<ModelName>` isn't revoked from the user.
275
+ * Accessing the collection of all models' schemata requires the resource `@hitchy.odem.schema` to be granted. This implicitly causes models with their [promote option](https://odem.hitchy.org/guides/defining-models.html#options) being `protected` to be exposed in responses to that request unless resource `@hitchy.odem.model.<ModelName>.promote` isn't revoked from the user.
276
276
 
277
277
  > In these examples, replace `<ModelName>` with a model's name in PascalCase.
278
278