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.
package/docs/writing-an-api.md
CHANGED
|
@@ -43,19 +43,24 @@ import AbstractApiModule from 'adapt-authoring-api'
|
|
|
43
43
|
|
|
44
44
|
class NotesModule extends AbstractApiModule {
|
|
45
45
|
async setValues () {
|
|
46
|
-
|
|
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
|
-
|
|
55
|
+
With a `routes.json` in the module root:
|
|
57
56
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
122
|
+
### Route configuration with routes.json
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
Create a `routes.json` file in your module's root directory. At minimum, you need to specify the `root` path:
|
|
121
125
|
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
437
|
+
await super.setValues()
|
|
423
438
|
this.permissionsScope = 'content' // Uses read:content, write:content
|
|
424
|
-
this.useDefaultRouteConfig()
|
|
425
439
|
}
|
|
426
440
|
```
|
|
427
441
|
|
package/lib/AbstractApiModule.js
CHANGED
|
@@ -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
|
|
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