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.
@@ -1,9 +1,9 @@
1
1
  import AbstractApiModule from 'adapt-authoring-api'
2
- import apidefs from './apidefs.js'
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 middleware = await this.app.waitForModule('middleware')
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
- return async (req, res, next) => {
431
- try {
432
- const plugin = await this.get({ name: req.apiData.query.type }) || {}
433
- const schema = await this.getSchema(plugin.schemaName)
434
- if (!schema) {
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.4.0",
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
- return async (req, res, next) => {
156
- try {
157
- const plugin = await this.get({ name: req.apiData.query.type }) || {}
158
- const schema = await this.getSchema(plugin.schemaName)
159
- if (!schema) {
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 return a middleware function', () => {
475
- const middleware = inst.serveSchema()
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 () => schemaData)
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
- const handler = inst.serveSchema()
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, schemaData)
499
- assert.equal(res.sendError.mock.callCount(), 0)
492
+ assert.deepEqual(sentJson, builtSchema)
493
+ assert.equal(next.mock.callCount(), 0)
500
494
  })
501
495
 
502
- it('should send NOT_FOUND error when schema is null', async () => {
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
- const handler = inst.serveSchema()
515
- await handler(req, res, next)
507
+ await inst.serveSchema(req, res, next)
516
508
 
517
- assert.equal(res.sendError.mock.callCount(), 1)
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
- const handler = inst.serveSchema()
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(res.sendError.mock.callCount(), 1)
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
- const handler = inst.serveSchema()
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)