adapt-authoring-api 3.1.0 → 3.1.2

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.
@@ -43,19 +43,24 @@ import AbstractApiModule from 'adapt-authoring-api'
43
43
 
44
44
  class NotesModule extends AbstractApiModule {
45
45
  async setValues () {
46
- this.root = 'notes'
46
+ await super.setValues()
47
47
  this.collectionName = 'notes'
48
48
  this.schemaName = 'note'
49
- this.useDefaultRouteConfig()
50
49
  }
51
50
  }
52
51
 
53
52
  export default NotesModule
54
53
  ```
55
54
 
56
- > **Tip:** For new modules, consider defining routes in a `routes.json` file instead of calling `useDefaultRouteConfig()`. See [Custom routes](#custom-routes) for details.
55
+ With a `routes.json` in the module root:
57
56
 
58
- With a schema at `schema/note.schema.json`:
57
+ ```json
58
+ {
59
+ "root": "notes"
60
+ }
61
+ ```
62
+
63
+ And a schema at `schema/note.schema.json`:
59
64
 
60
65
  ```json
61
66
  {
@@ -93,84 +98,84 @@ This creates the following endpoints:
93
98
 
94
99
  ### Required values
95
100
 
96
- Override `setValues()` to configure your module:
101
+ Override `setValues()` to configure your module. Always call `await super.setValues()` first — this loads route configuration from `routes.json` (if present) and applies the default CRUD routes.
97
102
 
98
103
  ```javascript
99
104
  async setValues () {
100
- this.root = 'notes' // URL path: /api/notes
105
+ await super.setValues()
101
106
  this.collectionName = 'notes' // MongoDB collection name
102
107
  this.schemaName = 'note' // Default schema for validation
103
- this.useDefaultRouteConfig() // Use standard CRUD routes
104
108
  }
105
109
  ```
106
110
 
107
111
  | Property | Type | Required | Description |
108
112
  | -------- | ---- | -------- | ----------- |
109
- | `root` | String | Yes* | URL path for the API (e.g., `notes` → `/api/notes`) |
113
+ | `root` | String | Yes* | URL path for the API (e.g., `notes` → `/api/notes`). Set via `routes.json`. |
110
114
  | `router` | Router | Yes* | Router instance (created automatically if `root` is set) |
111
- | `routes` | Array | Yes | Route definitions |
115
+ | `routes` | Array | Yes | Route definitions. Loaded from `routes.json` and `default-routes.json`. |
112
116
  | `collectionName` | String | Yes | MongoDB collection name |
113
117
  | `schemaName` | String | No | Default schema name for validation |
114
- | `permissionsScope` | String | No | Override the scope used for permissions (defaults to `root`) |
118
+ | `permissionsScope` | String | No | Override the scope used for permissions (defaults to `root`). Can be set in `routes.json`. |
115
119
 
116
120
  *Either `root` or `router` must be set.
117
121
 
118
- ### Using default routes
122
+ ### Route configuration with routes.json
119
123
 
120
- Call `useDefaultRouteConfig()` to use the standard CRUD routes:
124
+ Create a `routes.json` file in your module's root directory. At minimum, you need to specify the `root` path:
121
125
 
122
- ```javascript
123
- async setValues () {
124
- this.root = 'notes'
125
- this.collectionName = 'notes'
126
- this.schemaName = 'note'
127
- this.useDefaultRouteConfig()
126
+ ```json
127
+ {
128
+ "root": "notes"
128
129
  }
129
130
  ```
130
131
 
131
- The default routes are:
132
+ When `super.setValues()` is called, this file is loaded and merged with the default CRUD routes from `default-routes.json`. The default routes provide:
132
133
 
133
- ```javascript
134
- [
135
- {
136
- route: '/',
137
- handlers: { post: handler, get: queryHandler },
138
- permissions: { post: ['write:notes'], get: ['read:notes'] }
139
- },
140
- {
141
- route: '/schema',
142
- handlers: { get: serveSchema },
143
- permissions: { get: ['read:schema'] }
144
- },
145
- {
146
- route: '/:_id',
147
- handlers: { put: handler, get: handler, patch: handler, delete: handler },
148
- permissions: { put: ['write:notes'], get: ['read:notes'], patch: ['write:notes'], delete: ['write:notes'] }
149
- },
150
- {
151
- route: '/query',
152
- validate: false,
153
- modifying: false,
154
- handlers: { post: queryHandler },
155
- permissions: { post: ['read:notes'] }
156
- }
157
- ]
158
- ```
134
+ | Method | Route | Handler | Permissions |
135
+ | ------ | ----- | ------- | ----------- |
136
+ | POST | `/` | `requestHandler` | `write:${scope}` |
137
+ | GET | `/` | `queryHandler` | `read:${scope}` |
138
+ | GET | `/schema` | `serveSchema` | `read:schema` |
139
+ | PUT | `/:_id` | `requestHandler` | `write:${scope}` |
140
+ | GET | `/:_id` | `requestHandler` | `read:${scope}` |
141
+ | PATCH | `/:_id` | `requestHandler` | `write:${scope}` |
142
+ | DELETE | `/:_id` | `requestHandler` | `write:${scope}` |
143
+ | POST | `/query` | `queryHandler` | `read:${scope}` |
144
+
145
+ The `${scope}` placeholder is replaced with `permissionsScope` (if set) or `root`.
159
146
 
160
147
  ## Custom routes
161
148
 
162
- You can define custom routes by setting `this.routes` directly, or by adding to the default routes:
149
+ Custom routes are defined in your module's `routes.json` file alongside the default CRUD routes.
150
+
151
+ ### Adding routes via routes.json
163
152
 
164
- ### Adding routes to the defaults
153
+ Add a `routes` array to your `routes.json`. These are merged with the default CRUD routes:
154
+
155
+ ```json
156
+ {
157
+ "root": "notes",
158
+ "routes": [
159
+ {
160
+ "route": "/archive/:_id",
161
+ "handlers": { "post": "archiveHandler" },
162
+ "permissions": { "post": ["write:${scope}"] }
163
+ }
164
+ ]
165
+ }
166
+ ```
167
+
168
+ Handler values are method names on your module class — they are resolved automatically.
169
+
170
+ ### Adding routes programmatically
171
+
172
+ You can also add routes in `setValues()` after calling `super.setValues()`:
165
173
 
166
174
  ```javascript
167
175
  async setValues () {
168
- this.root = 'notes'
176
+ await super.setValues()
169
177
  this.collectionName = 'notes'
170
178
  this.schemaName = 'note'
171
- // initialise the defaults routes
172
- this.useDefaultRouteConfig()
173
- // Add custom route
174
179
  this.routes.push({
175
180
  route: '/archive/:_id',
176
181
  handlers: { post: this.archiveHandler.bind(this) },
@@ -181,12 +186,13 @@ async setValues () {
181
186
 
182
187
  ### Fully custom routes
183
188
 
189
+ To bypass the default CRUD routes entirely, override `this.routes` after `super.setValues()`:
190
+
184
191
  ```javascript
185
192
  async setValues () {
186
- this.root = 'notes'
193
+ await super.setValues()
187
194
  this.collectionName = 'notes'
188
195
  this.schemaName = 'note'
189
- // here we define our own completely custom list of routes, with no call to this.useDefaultRouteConfig
190
196
  this.routes = [
191
197
  {
192
198
  route: '/',
@@ -415,13 +421,21 @@ Routes are secured using permission scopes. The default routes use `read:<root>`
415
421
 
416
422
  ### Custom permission scope
417
423
 
418
- Set `permissionsScope` to use a different scope:
424
+ Set `permissionsScope` in your `routes.json` or in `setValues()`:
425
+
426
+ ```json
427
+ {
428
+ "root": "notes",
429
+ "permissionsScope": "content"
430
+ }
431
+ ```
432
+
433
+ Or programmatically:
419
434
 
420
435
  ```javascript
421
436
  async setValues () {
422
- this.root = 'notes'
437
+ await super.setValues()
423
438
  this.permissionsScope = 'content' // Uses read:content, write:content
424
- this.useDefaultRouteConfig()
425
439
  }
426
440
  ```
427
441
 
@@ -9,36 +9,6 @@ import { loadRouteConfig } from 'adapt-authoring-server'
9
9
  * @extends {AbstractModule}
10
10
  */
11
11
  class AbstractApiModule extends AbstractModule {
12
- get DEFAULT_ROUTES () {
13
- const readPerms = [`read:${this.permissionsScope || this.root}`]
14
- const writePerms = [`write:${this.permissionsScope || this.root}`]
15
- const handler = this.requestHandler.bind(this)
16
- return [
17
- {
18
- route: '/',
19
- handlers: { post: handler, get: this.queryHandler.bind(this) },
20
- permissions: { post: writePerms, get: readPerms }
21
- },
22
- {
23
- route: '/schema',
24
- handlers: { get: this.serveSchema.bind(this) },
25
- permissions: { get: ['read:schema'] }
26
- },
27
- {
28
- route: '/:_id',
29
- handlers: { put: handler, get: handler, patch: handler, delete: handler },
30
- permissions: { put: writePerms, get: readPerms, patch: writePerms, delete: writePerms }
31
- },
32
- {
33
- route: '/query',
34
- validate: false,
35
- modifying: false,
36
- handlers: { post: this.queryHandler.bind(this) },
37
- permissions: { post: readPerms }
38
- }
39
- ]
40
- }
41
-
42
12
  /**
43
13
  * Returns the 'OK' status code to match the HTTP method
44
14
  * @param {String} httpMethod
@@ -136,7 +106,7 @@ class AbstractApiModule extends AbstractModule {
136
106
  */
137
107
  this.routes = undefined
138
108
  /**
139
- * The scope to be used (see AbstractApiModule#useDefaultRouteConfig)
109
+ * The scope to be used for permissions
140
110
  * @type {String}
141
111
  */
142
112
  this.permissionsScope = undefined
@@ -172,21 +142,6 @@ class AbstractApiModule extends AbstractModule {
172
142
  })
173
143
  }
174
144
 
175
- /**
176
- * Uses default configuration for API routes
177
- * @example
178
- * POST /
179
- * GET /:_id?
180
- * PUT/DELETE /:_id
181
- */
182
- useDefaultRouteConfig () {
183
- if (!this.root) {
184
- return this.log('error', 'Must set API root before calling useDefaultConfig function')
185
- }
186
- /** @ignore */ this.routes = this.DEFAULT_ROUTES
187
- this.generateApiMetadata()
188
- }
189
-
190
145
  /**
191
146
  * Generates REST API metadata and stores on route config
192
147
  */
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Recursively replaces placeholder strings in an object tree.
3
+ * Non-string values (functions, numbers, booleans, null) pass through unchanged.
4
+ * @param {*} obj The value to process
5
+ * @param {Object<string,string>} replacements Map of placeholder to replacement value
6
+ * @returns {*} The value with all placeholders resolved
7
+ * @memberof api
8
+ */
9
+ export function replacePlaceholders (obj, replacements) {
10
+ if (typeof obj === 'string') {
11
+ return Object.entries(replacements).reduce((s, [k, v]) => v != null ? s.replaceAll(k, v) : s, obj)
12
+ }
13
+ if (Array.isArray(obj)) return obj.map(item => replacePlaceholders(item, replacements))
14
+ if (obj && typeof obj === 'object' && obj.constructor === Object) {
15
+ return Object.fromEntries(
16
+ Object.entries(obj).map(([k, v]) => [k, replacePlaceholders(v, replacements)])
17
+ )
18
+ }
19
+ return obj
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-api",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "Abstract module for creating APIs",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-api",
6
6
  "license": "GPL-3.0",
@@ -17,10 +17,6 @@
17
17
  "type": "boolean",
18
18
  "description": "Whether to generate default CRUD routes",
19
19
  "default": true
20
- },
21
- "routes": {
22
- "type": "array",
23
- "items": { "$ref": "routeitem" }
24
20
  }
25
21
  }
26
22
  }