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.
package/errors/errors.json
CHANGED
|
@@ -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
|
}
|
package/lib/AbstractApiModule.js
CHANGED
|
@@ -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 =
|
|
359
|
-
const postCheck =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -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;
|