adapt-authoring-contentplugin 1.4.0 → 1.5.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.
- package/lib/ContentPluginModule.js +16 -45
- package/package.json +3 -2
- package/routes.json +76 -0
- package/tests/ContentPluginModule.spec.js +27 -38
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import AbstractApiModule from 'adapt-authoring-api'
|
|
2
|
-
import
|
|
3
|
-
import fs from 'fs/promises'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
4
3
|
import { glob } from 'glob'
|
|
5
|
-
import path from 'path'
|
|
4
|
+
import path from 'node:path'
|
|
6
5
|
import { readJson } from 'adapt-authoring-core'
|
|
6
|
+
import { loadRouteConfig } from 'adapt-authoring-server'
|
|
7
7
|
import {
|
|
8
8
|
backupPluginVersion,
|
|
9
9
|
getMostRecentBackup,
|
|
@@ -34,37 +34,8 @@ class ContentPluginModule extends AbstractApiModule {
|
|
|
34
34
|
*/
|
|
35
35
|
this.newPlugins = []
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
this.useDefaultRouteConfig()
|
|
40
|
-
// remove unnecessary routes
|
|
41
|
-
delete this.routes.find(r => r.route === '/').handlers.post
|
|
42
|
-
delete this.routes.find(r => r.route === '/:_id').handlers.put
|
|
43
|
-
// extra routes
|
|
44
|
-
this.routes.push({
|
|
45
|
-
route: '/install',
|
|
46
|
-
handlers: {
|
|
47
|
-
post: [
|
|
48
|
-
middleware.fileUploadParser(middleware.zipTypes, { unzip: true }),
|
|
49
|
-
this.installHandler.bind(this)
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
permissions: { post: [`install:${this.root}`] },
|
|
53
|
-
validate: false,
|
|
54
|
-
meta: apidefs.install
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
route: '/:_id/update',
|
|
58
|
-
handlers: { post: this.updateHandler.bind(this) },
|
|
59
|
-
permissions: { post: [`update:${this.root}`] },
|
|
60
|
-
meta: apidefs.update
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
route: '/:_id/uses',
|
|
64
|
-
handlers: { get: this.usesHandler.bind(this) },
|
|
65
|
-
permissions: { get: [`read:${this.root}`] },
|
|
66
|
-
meta: apidefs.uses
|
|
67
|
-
})
|
|
37
|
+
const config = await loadRouteConfig(this.rootDir, this, { schema: 'apiroutes' })
|
|
38
|
+
if (config) this.applyRouteConfig(config)
|
|
68
39
|
}
|
|
69
40
|
|
|
70
41
|
/** @override */
|
|
@@ -426,18 +397,16 @@ class ContentPluginModule extends AbstractApiModule {
|
|
|
426
397
|
}
|
|
427
398
|
|
|
428
399
|
/** @override */
|
|
429
|
-
serveSchema () {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return res.sendError(this.app.errors.NOT_FOUND.setData({ type: 'schema', id: plugin.schemaName }))
|
|
436
|
-
}
|
|
437
|
-
res.type('application/schema+json').json(schema)
|
|
438
|
-
} catch (e) {
|
|
439
|
-
return next(e)
|
|
400
|
+
async serveSchema (req, res, next) {
|
|
401
|
+
try {
|
|
402
|
+
const plugin = await this.get({ name: req.apiData.query.type }) || {}
|
|
403
|
+
const schema = await this.getSchema(plugin.schemaName)
|
|
404
|
+
if (!schema) {
|
|
405
|
+
return next(this.app.errors.NO_SCHEMA_DEF)
|
|
440
406
|
}
|
|
407
|
+
res.type('application/schema+json').json(schema.built)
|
|
408
|
+
} catch (e) {
|
|
409
|
+
return next(e)
|
|
441
410
|
}
|
|
442
411
|
}
|
|
443
412
|
|
|
@@ -449,6 +418,8 @@ class ContentPluginModule extends AbstractApiModule {
|
|
|
449
418
|
*/
|
|
450
419
|
async installHandler (req, res, next) {
|
|
451
420
|
try {
|
|
421
|
+
const middleware = await this.app.waitForModule('middleware')
|
|
422
|
+
await middleware.fileUploadParser(middleware.zipTypes, { unzip: true, promisify: true })(req, res)
|
|
452
423
|
const [pluginData] = await this.installPlugins([
|
|
453
424
|
[
|
|
454
425
|
req.body.name,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-contentplugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Module for managing framework plugins",
|
|
5
5
|
"homepage": "https://github.com/adapt-security/adapt-authoring-contentplugin",
|
|
6
6
|
"repository": "github:adapt-security/adapt-authoring-contentplugin",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"adapt-authoring-content": "^2.0.0",
|
|
21
21
|
"adapt-authoring-jsonschema": "^1.2.0",
|
|
22
22
|
"adapt-authoring-middleware": "^1.0.2",
|
|
23
|
-
"adapt-authoring-mongodb": "^3.0.0"
|
|
23
|
+
"adapt-authoring-mongodb": "^3.0.0",
|
|
24
|
+
"adapt-authoring-server": "^2.1.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@semantic-release/git": "^10.0.1",
|
package/routes.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Routes are defined explicitly rather than using defaults to omit POST / and PUT /:_id",
|
|
3
|
+
"root": "contentplugins",
|
|
4
|
+
"routes": [
|
|
5
|
+
{
|
|
6
|
+
"route": "/",
|
|
7
|
+
"handlers": { "get": "requestHandler" },
|
|
8
|
+
"permissions": { "get": ["read:${scope}"] }
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"route": "/schema",
|
|
12
|
+
"handlers": { "get": "serveSchema" },
|
|
13
|
+
"permissions": { "get": ["read:schema"] }
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"route": "/:_id",
|
|
17
|
+
"handlers": { "get": "requestHandler", "patch": "requestHandler", "delete": "requestHandler" },
|
|
18
|
+
"permissions": { "get": ["read:${scope}"], "patch": ["write:${scope}"], "delete": ["write:${scope}"] }
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"route": "/query",
|
|
22
|
+
"validate": false,
|
|
23
|
+
"modifying": false,
|
|
24
|
+
"handlers": { "post": "queryHandler" },
|
|
25
|
+
"permissions": { "post": ["read:${scope}"] }
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"route": "/install",
|
|
29
|
+
"validate": false,
|
|
30
|
+
"handlers": { "post": "installHandler" },
|
|
31
|
+
"permissions": { "post": ["install:${scope}"] },
|
|
32
|
+
"meta": {
|
|
33
|
+
"post": {
|
|
34
|
+
"summary": "Install a content plugin",
|
|
35
|
+
"requestBody": {
|
|
36
|
+
"content": {
|
|
37
|
+
"application/json": {
|
|
38
|
+
"schema": {
|
|
39
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"name": { "type": "string" },
|
|
43
|
+
"version": { "type": "string" },
|
|
44
|
+
"force": { "type": "boolean", "default": false }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"route": "/:_id/update",
|
|
55
|
+
"handlers": { "post": "updateHandler" },
|
|
56
|
+
"permissions": { "post": ["update:${scope}"] },
|
|
57
|
+
"meta": {
|
|
58
|
+
"post": {
|
|
59
|
+
"summary": "Update a single content plugin",
|
|
60
|
+
"parameters": [{ "name": "_id", "in": "path", "description": "Content plugin _id", "required": true }]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"route": "/:_id/uses",
|
|
66
|
+
"handlers": { "get": "usesHandler" },
|
|
67
|
+
"permissions": { "get": ["read:${scope}"] },
|
|
68
|
+
"meta": {
|
|
69
|
+
"get": {
|
|
70
|
+
"summary": "Return courses using a single content plugin",
|
|
71
|
+
"parameters": [{ "name": "_id", "in": "path", "description": "Content plugin _id", "required": true }]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -65,6 +65,9 @@ function createInstance (overrides = {}) {
|
|
|
65
65
|
NOT_FOUND: Object.assign(new Error('NOT_FOUND'), {
|
|
66
66
|
code: 'NOT_FOUND',
|
|
67
67
|
setData: mock.fn(function (d) { this.data = d; return this })
|
|
68
|
+
}),
|
|
69
|
+
NO_SCHEMA_DEF: Object.assign(new Error('NO_SCHEMA_DEF'), {
|
|
70
|
+
code: 'NO_SCHEMA_DEF'
|
|
68
71
|
})
|
|
69
72
|
}
|
|
70
73
|
},
|
|
@@ -151,18 +154,16 @@ async function installPlugins (plugins, options = { strict: false, force: false
|
|
|
151
154
|
return installed
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
function serveSchema () {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return res.sendError(this.app.errors.NOT_FOUND.setData({ type: 'schema', id: plugin.schemaName }))
|
|
161
|
-
}
|
|
162
|
-
res.type('application/schema+json').json(schema)
|
|
163
|
-
} catch (e) {
|
|
164
|
-
return next(e)
|
|
157
|
+
async function serveSchema (req, res, next) {
|
|
158
|
+
try {
|
|
159
|
+
const plugin = await this.get({ name: req.apiData.query.type }) || {}
|
|
160
|
+
const schema = await this.getSchema(plugin.schemaName)
|
|
161
|
+
if (!schema) {
|
|
162
|
+
return next(this.app.errors.NO_SCHEMA_DEF)
|
|
165
163
|
}
|
|
164
|
+
res.type('application/schema+json').json(schema.built)
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return next(e)
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -471,50 +472,41 @@ describe('ContentPluginModule', () => {
|
|
|
471
472
|
// serveSchema
|
|
472
473
|
// -----------------------------------------------------------------------
|
|
473
474
|
describe('serveSchema()', () => {
|
|
474
|
-
it('should
|
|
475
|
-
const
|
|
476
|
-
assert.equal(typeof middleware, 'function')
|
|
477
|
-
})
|
|
478
|
-
|
|
479
|
-
it('should send schema JSON when found', async () => {
|
|
480
|
-
const schemaData = { type: 'object', properties: {} }
|
|
475
|
+
it('should send schema.built JSON when found', async () => {
|
|
476
|
+
const builtSchema = { type: 'object', properties: {} }
|
|
481
477
|
inst.get = mock.fn(async () => ({ schemaName: 'mySchema' }))
|
|
482
|
-
inst.getSchema = mock.fn(async () =>
|
|
478
|
+
inst.getSchema = mock.fn(async () => ({ built: builtSchema }))
|
|
483
479
|
|
|
484
480
|
const req = { apiData: { query: { type: 'myPlugin' } } }
|
|
485
481
|
let sentType = null
|
|
486
482
|
let sentJson = null
|
|
487
483
|
const res = {
|
|
488
484
|
type: mock.fn(function (t) { sentType = t; return this }),
|
|
489
|
-
json: mock.fn((data) => { sentJson = data })
|
|
490
|
-
sendError: mock.fn()
|
|
485
|
+
json: mock.fn((data) => { sentJson = data })
|
|
491
486
|
}
|
|
492
487
|
const next = mock.fn()
|
|
493
488
|
|
|
494
|
-
|
|
495
|
-
await handler(req, res, next)
|
|
489
|
+
await inst.serveSchema(req, res, next)
|
|
496
490
|
|
|
497
491
|
assert.equal(sentType, 'application/schema+json')
|
|
498
|
-
assert.deepEqual(sentJson,
|
|
499
|
-
assert.equal(
|
|
492
|
+
assert.deepEqual(sentJson, builtSchema)
|
|
493
|
+
assert.equal(next.mock.callCount(), 0)
|
|
500
494
|
})
|
|
501
495
|
|
|
502
|
-
it('should
|
|
496
|
+
it('should call next with NOT_FOUND error when schema is null', async () => {
|
|
503
497
|
inst.get = mock.fn(async () => ({ schemaName: 'missing' }))
|
|
504
498
|
inst.getSchema = mock.fn(async () => null)
|
|
505
499
|
|
|
506
500
|
const req = { apiData: { query: { type: 'myPlugin' } } }
|
|
507
501
|
const res = {
|
|
508
502
|
type: mock.fn(function () { return this }),
|
|
509
|
-
json: mock.fn()
|
|
510
|
-
sendError: mock.fn()
|
|
503
|
+
json: mock.fn()
|
|
511
504
|
}
|
|
512
505
|
const next = mock.fn()
|
|
513
506
|
|
|
514
|
-
|
|
515
|
-
await handler(req, res, next)
|
|
507
|
+
await inst.serveSchema(req, res, next)
|
|
516
508
|
|
|
517
|
-
assert.equal(
|
|
509
|
+
assert.equal(next.mock.callCount(), 1)
|
|
518
510
|
})
|
|
519
511
|
|
|
520
512
|
it('should use empty object fallback when get returns null', async () => {
|
|
@@ -524,17 +516,15 @@ describe('ContentPluginModule', () => {
|
|
|
524
516
|
const req = { apiData: { query: { type: 'unknown' } } }
|
|
525
517
|
const res = {
|
|
526
518
|
type: mock.fn(function () { return this }),
|
|
527
|
-
json: mock.fn()
|
|
528
|
-
sendError: mock.fn()
|
|
519
|
+
json: mock.fn()
|
|
529
520
|
}
|
|
530
521
|
const next = mock.fn()
|
|
531
522
|
|
|
532
|
-
|
|
533
|
-
await handler(req, res, next)
|
|
523
|
+
await inst.serveSchema(req, res, next)
|
|
534
524
|
|
|
535
525
|
// getSchema should be called with undefined (from {}.schemaName)
|
|
536
526
|
assert.equal(inst.getSchema.mock.calls[0].arguments[0], undefined)
|
|
537
|
-
assert.equal(
|
|
527
|
+
assert.equal(next.mock.callCount(), 1)
|
|
538
528
|
})
|
|
539
529
|
|
|
540
530
|
it('should call next with the error when an exception occurs', async () => {
|
|
@@ -545,8 +535,7 @@ describe('ContentPluginModule', () => {
|
|
|
545
535
|
const res = { sendError: mock.fn() }
|
|
546
536
|
const next = mock.fn()
|
|
547
537
|
|
|
548
|
-
|
|
549
|
-
await handler(req, res, next)
|
|
538
|
+
await inst.serveSchema(req, res, next)
|
|
550
539
|
|
|
551
540
|
assert.equal(next.mock.callCount(), 1)
|
|
552
541
|
assert.equal(next.mock.calls[0].arguments[0], testError)
|