adapt-authoring-api 1.0.1 → 1.2.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.
@@ -28,5 +28,14 @@
28
28
  "NO_SCHEMA_DEF": {
29
29
  "description": "No json schema has been defined",
30
30
  "statusCode": 404
31
+ },
32
+ "TOO_MANY_RESULTS": {
33
+ "data": {
34
+ "actual": "Number of actual results",
35
+ "expected": "Number of expected results",
36
+ "query": "The query"
37
+ },
38
+ "description": "Too many results were returned",
39
+ "statusCode": 500
31
40
  }
32
41
  }
@@ -37,6 +37,7 @@ class AbstractApiModule extends AbstractModule {
37
37
  }
38
38
  ]
39
39
  }
40
+
40
41
  /**
41
42
  * Returns the 'OK' status code to match the HTTP method
42
43
  * @param {String} httpMethod
@@ -355,8 +356,8 @@ class AbstractApiModule extends AbstractModule {
355
356
  let data
356
357
  try {
357
358
  await this.requestHook.invoke(req)
358
- const preCheck = !req.auth.isSuper && method !== 'get' && method !== 'post'
359
- const postCheck = !req.auth.isSuper && method === 'get'
359
+ const preCheck = method !== 'get' && method !== 'post'
360
+ const postCheck = method === 'get'
360
361
  if (preCheck) {
361
362
  await this.checkAccess(req, req.apiData.query)
362
363
  }
@@ -374,7 +375,7 @@ class AbstractApiModule extends AbstractModule {
374
375
  }
375
376
  data = data[0]
376
377
  }
377
- if (method !== 'get') {
378
+ if (method !== 'get') {
378
379
  const resource = Array.isArray(data) ? req.apiData.query : data._id.toString()
379
380
  this.log('debug', `API_${func.name.toUpperCase()}`, resource, 'by', req.auth.user._id.toString())
380
381
  }
@@ -561,9 +562,23 @@ class AbstractApiModule extends AbstractModule {
561
562
  */
562
563
  async find (query, options = {}, mongoOptions = {}) {
563
564
  this.setDefaultOptions(options)
564
- const mongodb = await this.app.waitForModule('mongodb')
565
565
  const q = options.validate ? await this.parseQuery(options.schemaName, query, options, mongoOptions) : query
566
- return mongodb.find(options.collectionName, q, mongoOptions)
566
+ return this.cache.get(q, options, mongoOptions)
567
+ }
568
+
569
+ /**
570
+ * Retrieves a single document from the DB. If multiple results are returned from the query, an error is thrown.
571
+ * @param {Object} query Attributes to use to filter DB documents
572
+ * @param {FindOptions} options Function options
573
+ * @param {external:MongoDBFindOptions} mongoOptions Options to be passed to the MongoDB function
574
+ * @return {Promise} Resolves with the single DB document
575
+ */
576
+ async findOne (query, options, mongoOptions) {
577
+ const results = await this.find(query, options, mongoOptions)
578
+ if (results.length > 1) {
579
+ throw this.app.errors.TOO_MANY_RESULTS.setData({ actual: results.length, expected: 1, query })
580
+ }
581
+ return results[0]
567
582
  }
568
583
 
569
584
  /**
@@ -596,7 +611,7 @@ class AbstractApiModule extends AbstractModule {
596
611
  if (options.invokePostHook !== false) await this.postUpdateHook.invoke(originalDoc, results)
597
612
  return results
598
613
  }
599
-
614
+
600
615
  /**
601
616
  * Updates existing documents in the DB
602
617
  * @param {Object} query Attributes to use to filter DB documents
package/lib/DataCache.js CHANGED
@@ -6,7 +6,7 @@ import { App } from 'adapt-authoring-core'
6
6
  class DataCache {
7
7
  /** @override */
8
8
  constructor ({ enable, lifespan }) {
9
- this.isEnabled = enable !== false
9
+ this.isEnabled = enable === true
10
10
  this.lifespan = lifespan ?? App.instance.config.get('adapt-authoring-api.defaultCacheLifespan')
11
11
  this.cache = {}
12
12
  }
@@ -21,7 +21,7 @@ class DataCache {
21
21
  async get (query, options, mongoOptions) {
22
22
  const key = JSON.stringify(query) + JSON.stringify(options) + JSON.stringify(mongoOptions)
23
23
  this.prune()
24
- if (this.cache[key]) {
24
+ if (this.isEnabled && this.cache[key]) {
25
25
  return this.cache[key].data
26
26
  }
27
27
  const mongodb = await App.instance.waitForModule('mongodb')
@@ -35,8 +35,7 @@ class DataCache {
35
35
  */
36
36
  prune () {
37
37
  Object.keys(this.cache).forEach(k => {
38
- const cache = this.cache[k]
39
- if (Date.now() > (cache.timestamp + this.lifespan)) {
38
+ if (Date.now() > (this.cache[k].timestamp + this.lifespan)) {
40
39
  delete this.cache[k]
41
40
  }
42
41
  })
package/lib/typedefs.js CHANGED
@@ -64,4 +64,4 @@
64
64
  * @typedef {Object} DeleteOptions
65
65
  * @property {String} collectionName DB collection to remove document from
66
66
  * @property {String} invokePostHook Whether the function should invoke the 'post' action hook on success
67
- */
67
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-api",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
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",
@@ -1,84 +0,0 @@
1
- const { App } = require('adapt-authoring-core');
2
- const should = require('should');
3
- const TestApiModule = require('./data/testApiModule');
4
-
5
- describe('Abstract API module', function() {
6
- before(function(done) {
7
- const loadModule = (mod, done) => {
8
- const m = this.app.getModule(mod);
9
- m.preload (this.app, () => m.boot(this.app, done, done), done);
10
- };
11
- this.app = App.instance;
12
-
13
- loadModule('server', () => loadModule('mongodb', done));
14
-
15
- this.tmi = new TestApiModule(this.app, {});
16
- this.tmi.router.should.not.be.undefined();
17
- });
18
- describe('#requestHandler()', function() {
19
- it('should customise the request object', function() {
20
- const req = { method: 'GET' };
21
- TestApiModule.requestHandler()(req, {}, () => {});
22
- should.exist(req.type);
23
- should.exist(req.dsquery);
24
- });
25
- it('should correctly map HTTP methods to MongoDBModule functions', function() {
26
- const m1 = this.app.getModule('mongodb');
27
- const m2 = {
28
- retrieve: () => {
29
- return new Promise((resolve, reject) => { correctlyMapped = true; })
30
- }
31
- };
32
- let correctlyMapped = false;
33
- this.app.dependencyloader.modules['adapt-authoring-mongodb'] = m2;
34
- TestApiModule.requestHandler()({ method: 'GET' }, {}, () => {});
35
- this.app.dependencyloader.modules['adapt-authoring-mongodb'] = m1;
36
- correctlyMapped.should.be.true();
37
- });
38
- });
39
- describe('#preload()', function() {
40
- it('should create a child Router with the correct route', function(done) {
41
- this.tmi.preload(this.app, () => {
42
- this.tmi.router.constructor.name.should.equal('Router');
43
- done();
44
- }, done);
45
- });
46
- });
47
- describe('#initSchemas()', function() {
48
- it('should add specified schemas to the DB', function(done) {
49
- this.tmi.boot(this.app, () => {
50
- should.exist(this.app.getModule('mongodb').connection.models.test);
51
- done();
52
- }, done);
53
- });
54
- it('should ignore badly configured data', function() {
55
- const mongoModels = this.app.getModule('mongodb').connection.models;
56
- Object.keys(mongoModels).length.should.equal(1);
57
- });
58
- });
59
- describe('#initMiddleware()', function() {
60
- it('should add middleware to the API router', function() {
61
- const middleware = this.tmi.router.middleware;
62
- middleware.length.should.equal(1);
63
- middleware[0].name.should.equal('testMiddleware');
64
- });
65
- });
66
- describe('#initRoutes()', function() {
67
- it('should add routes defined as an object', function() {
68
- const routes = this.tmi.router.routes.map(r => r.route);
69
- routes.should.containEql('/objectroute');
70
- });
71
- it('should add routes defined as an array', function() {
72
- const routes = this.tmi.router.routes.map(r => r.route);
73
- routes.should.containEql('/arrayroute');
74
- });
75
- it('should set custom permissions scopes if specified', function() {
76
- const postScopes = this.app.auth.routes.secure['/api/arrayroute'].post;
77
- postScopes.should.containEql('testscope');
78
- });
79
- it('should set generic permissions scopes if not specified', function() {
80
- const scopes = this.app.auth.routes.secure['/api/objectroute'];
81
- scopes.should.not.be.undefined();
82
- });
83
- });
84
- });
@@ -1,49 +0,0 @@
1
- const should = require('should');
2
-
3
- describe('Abstract API utilities', function() {
4
- describe('#callDbFunction()', function() {
5
- it('should fail if request parameter doesn\'t specify a type', function() {
6
- false.should.be.true();
7
- });
8
- it('should fail if attempting to call an unknown DB function', function() {
9
- false.should.be.true();
10
- });
11
- it('should return data in response', function() {
12
- false.should.be.true();
13
- });
14
- it('should set response HTTP status', function() {
15
- false.should.be.true();
16
- });
17
- });
18
- describe('#httpMethodToAction()', function() {
19
- it('should return a string', function() {
20
- false.should.be.true();
21
- });
22
- it('should return action string for known action', function() {
23
- false.should.be.true();
24
- });
25
- it('should return empty string for unknown action', function() {
26
- false.should.be.true();
27
- });
28
- });
29
- describe('#validateSchemaDef()', function() {
30
- it('should fail if def isn\'t an object', function() {
31
- false.should.be.true();
32
- });
33
- it('should fail if def has no name', function() {
34
- false.should.be.true();
35
- });
36
- it('should fail if def has no model', function() {
37
- false.should.be.true();
38
- });
39
- it('should fail if schemas isn\'t an array', function() {
40
- false.should.be.true();
41
- });
42
- it('should fail if def has no routes', function() {
43
- false.should.be.true();
44
- });
45
- it('should validate routes', function() {
46
- false.should.be.true();
47
- });
48
- });
49
- });
@@ -1,50 +0,0 @@
1
- const AbstractAPIModule = require('../../lib/AbstractApiModule');
2
-
3
- const TestSchema = {
4
- name: 'test',
5
- definition: {
6
- isTest: {
7
- type: "boolean", default: true
8
- }
9
- }
10
- };
11
-
12
- class TestApiModule extends AbstractAPIModule {
13
- static get def() {
14
- return {
15
- name: 'test',
16
- model: 'test',
17
- schemas: [
18
- TestSchema,
19
- { name: 't2' },
20
- { definition: {} }
21
- ],
22
- middleware: [testMiddleware],
23
- routes: [
24
- {
25
- route: '/arrayroute',
26
- handlers: ['post','get','put','delete'],
27
- scopes: { post: 'testscope' }
28
- },
29
- {
30
- route: '/objectroute',
31
- handlers: {
32
- post: testRouteHandler,
33
- get: testRouteHandler,
34
- put: testRouteHandler,
35
- delete: testRouteHandler
36
- }
37
- }
38
- ]
39
- };
40
- }
41
- }
42
-
43
- function testMiddleware(req, res, next) {
44
- console.log('Test middleware called');
45
- }
46
- function testRouteHandler(req, res, next) {
47
- console.log('Test handler called');
48
- }
49
-
50
- module.exports = TestApiModule;