amphora 8.0.0-0 → 8.2.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/.circleci/config.yml +0 -1
- package/lib/bootstrap.test.js +0 -3
- package/lib/render.js +6 -2
- package/lib/render.test.js +3 -0
- package/lib/responses.js +45 -8
- package/lib/responses.test.js +19 -3
- package/lib/services/composer.js +4 -3
- package/lib/services/composer.test.js +2 -3
- package/lib/services/db-operations.js +28 -2
- package/lib/services/pages.js +8 -4
- package/package.json +5 -5
package/.circleci/config.yml
CHANGED
package/lib/bootstrap.test.js
CHANGED
|
@@ -12,7 +12,6 @@ const _ = require('lodash'),
|
|
|
12
12
|
|
|
13
13
|
describe(_.startCase(filename), function () {
|
|
14
14
|
let sandbox,
|
|
15
|
-
bootstrapFake, // eslint-disable-line
|
|
16
15
|
db,
|
|
17
16
|
sitesFake,
|
|
18
17
|
fakeLog;
|
|
@@ -41,8 +40,6 @@ describe(_.startCase(filename), function () {
|
|
|
41
40
|
prefix: 'example4.com',
|
|
42
41
|
subsite: 'example4'
|
|
43
42
|
}];
|
|
44
|
-
|
|
45
|
-
bootstrapFake = files.getYaml(path.resolve('./test/fixtures/config/bootstrap'));
|
|
46
43
|
});
|
|
47
44
|
|
|
48
45
|
beforeEach(function () {
|
package/lib/render.js
CHANGED
|
@@ -87,8 +87,12 @@ function renderComponent(req, res, hrStart) {
|
|
|
87
87
|
return components.get(_ref, locals)
|
|
88
88
|
.then(cmptData => formDataForRenderer(cmptData, { _ref }, locals))
|
|
89
89
|
.tap(logTime(hrStart, _ref))
|
|
90
|
-
.then(({data, options}) => renderer.render(data, options, res))
|
|
91
|
-
.catch(err =>
|
|
90
|
+
.then(({ data, options }) => renderer.render(data, options, res))
|
|
91
|
+
.catch(err => {
|
|
92
|
+
log('error', err, { url: _.get(res, 'locals.url') });
|
|
93
|
+
|
|
94
|
+
return responses.handleError(res)(err);
|
|
95
|
+
});
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
/**
|
package/lib/render.test.js
CHANGED
|
@@ -7,6 +7,7 @@ const _ = require('lodash'),
|
|
|
7
7
|
expect = require('chai').expect,
|
|
8
8
|
sinon = require('sinon'),
|
|
9
9
|
schema = require('./schema'),
|
|
10
|
+
responses = require('./responses'),
|
|
10
11
|
components = require('./services/components'),
|
|
11
12
|
composer = require('./services/composer'),
|
|
12
13
|
createMockReq = require('../test/fixtures/mocks/req'),
|
|
@@ -81,6 +82,7 @@ describe(_.startCase(filename), function () {
|
|
|
81
82
|
sandbox.stub(components);
|
|
82
83
|
sandbox.stub(composer);
|
|
83
84
|
sandbox.stub(schema);
|
|
85
|
+
sandbox.stub(responses, 'handleError').returns(() => {});
|
|
84
86
|
lib.setLog(mockLog);
|
|
85
87
|
lib.resetUriRouteHandlers();
|
|
86
88
|
lib.registerRenderers({
|
|
@@ -177,6 +179,7 @@ describe(_.startCase(filename), function () {
|
|
|
177
179
|
return fn(req, getMockRes())
|
|
178
180
|
.then(() => {
|
|
179
181
|
sinon.assert.calledWith(mockLog, 'error', error);
|
|
182
|
+
sinon.assert.calledOnce(responses.handleError);
|
|
180
183
|
lib.registerRenderers(undefined);
|
|
181
184
|
});
|
|
182
185
|
});
|
package/lib/responses.js
CHANGED
|
@@ -9,6 +9,25 @@ const _ = require('lodash'),
|
|
|
9
9
|
filter = require('through2-filter'),
|
|
10
10
|
metaController = require('./services/metadata');
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Returns a the name of the log level appropriate for a given HTTP response code.
|
|
14
|
+
* - 5xx = error
|
|
15
|
+
* - 4xx = warn
|
|
16
|
+
* - default = info
|
|
17
|
+
*
|
|
18
|
+
* @param {number} code: An HTTP status code.
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function logLevelFromStatusCode(code) {
|
|
22
|
+
if (code >= 500) {
|
|
23
|
+
return 'error';
|
|
24
|
+
}
|
|
25
|
+
if (code >= 400) {
|
|
26
|
+
return 'warn';
|
|
27
|
+
}
|
|
28
|
+
return 'info';
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
/**
|
|
13
32
|
* Finds prefixToken, and removes it and anything before it.
|
|
14
33
|
*
|
|
@@ -283,17 +302,17 @@ function notFound(err, res) {
|
|
|
283
302
|
* @param {object} res
|
|
284
303
|
*/
|
|
285
304
|
function serverError(err, res) {
|
|
286
|
-
// error is required to be logged
|
|
287
|
-
log('error', err.message, { stack: err.stack, url: _.get(res, 'locals.url') });
|
|
288
|
-
|
|
289
305
|
const message = err.message || 'Server Error', // completely hide these messages from outside
|
|
290
|
-
code = 500
|
|
306
|
+
code = 500,
|
|
307
|
+
level = logLevelFromStatusCode(code);
|
|
308
|
+
|
|
309
|
+
log(level, err.message, { code, stack: err.stack, url: _.get(res, 'locals.url') });
|
|
291
310
|
|
|
292
311
|
sendDefaultResponseForCode(code, message, res);
|
|
293
312
|
}
|
|
294
313
|
|
|
295
314
|
/**
|
|
296
|
-
* All
|
|
315
|
+
* All unauthorized requests should look like this.
|
|
297
316
|
*
|
|
298
317
|
* @param {object} res
|
|
299
318
|
*/
|
|
@@ -312,14 +331,32 @@ function unauthorized(res) {
|
|
|
312
331
|
* @param {object} res
|
|
313
332
|
*/
|
|
314
333
|
function clientError(err, res) {
|
|
315
|
-
log('error', err.message, { stack: err.stack, url: _.get(res, 'locals.url') });
|
|
316
334
|
// They know it's a 400 already, we don't need to repeat the fact that its an error.
|
|
317
335
|
const message = removePrefix(err.message, ':'),
|
|
318
|
-
code = 400
|
|
336
|
+
code = 400,
|
|
337
|
+
level = logLevelFromStatusCode(code);
|
|
338
|
+
|
|
339
|
+
log(level, err.message, { code, stack: err.stack, url: _.get(res, 'locals.url') });
|
|
319
340
|
|
|
320
341
|
sendDefaultResponseForCode(code, message, res);
|
|
321
342
|
}
|
|
322
343
|
|
|
344
|
+
/**
|
|
345
|
+
* This function dispatches an errors that have a manually set .status attribute.
|
|
346
|
+
* The .status attribute indicates that the error was intentionally constructed
|
|
347
|
+
* or caught and assigned a specific response code/message.
|
|
348
|
+
*
|
|
349
|
+
* @param {Error} err
|
|
350
|
+
* @param {object} res
|
|
351
|
+
*/
|
|
352
|
+
function explicitError(err, res) {
|
|
353
|
+
const code = err.status,
|
|
354
|
+
level = logLevelFromStatusCode(code);
|
|
355
|
+
|
|
356
|
+
log(level, err.message, { code, stack: err.stack, url: _.get(res, 'locals.url') });
|
|
357
|
+
sendDefaultResponseForCode(code, err.message, res);
|
|
358
|
+
}
|
|
359
|
+
|
|
323
360
|
/**
|
|
324
361
|
* Handle errors in the standard/generic way
|
|
325
362
|
* @param {object} res
|
|
@@ -330,7 +367,7 @@ function handleError(res) {
|
|
|
330
367
|
if (err.status && err.name !== 'NotFoundError') {
|
|
331
368
|
// If we're in this block, the error has a defined `status` property and
|
|
332
369
|
// the error should be directed out immediately
|
|
333
|
-
|
|
370
|
+
explicitError(err, res);
|
|
334
371
|
} else if (err.name === 'NotFoundError' ||
|
|
335
372
|
err.message.indexOf('ENOENT') !== -1 ||
|
|
336
373
|
err.message.indexOf('not found') !== -1) {
|
package/lib/responses.test.js
CHANGED
|
@@ -111,13 +111,29 @@ describe(_.startCase(filename), function () {
|
|
|
111
111
|
fn(res)(new Error('something'));
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
it('sends the error code defined in the `status` property', function (done) {
|
|
114
|
+
it('sends the error code defined in the `status` property, 4xx', function (done) {
|
|
115
115
|
const res = createMockRes(),
|
|
116
116
|
myError = new Error('something');
|
|
117
117
|
|
|
118
118
|
myError.status = 403;
|
|
119
119
|
expectStatus(res, 403);
|
|
120
|
-
expectResult(res, 'sendStatus: whatever',
|
|
120
|
+
expectResult(res, 'sendStatus: whatever', function () {
|
|
121
|
+
sinon.assert.calledWith(fakeLog, 'warn');
|
|
122
|
+
done();
|
|
123
|
+
});
|
|
124
|
+
fn(res)(myError);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('sends the error code defined in the `status` property, 5xx', function (done) {
|
|
128
|
+
const res = createMockRes(),
|
|
129
|
+
myError = new Error('something');
|
|
130
|
+
|
|
131
|
+
myError.status = 500;
|
|
132
|
+
expectStatus(res, 500);
|
|
133
|
+
expectResult(res, 'sendStatus: whatever', function () {
|
|
134
|
+
sinon.assert.calledWith(fakeLog, 'error');
|
|
135
|
+
done();
|
|
136
|
+
});
|
|
121
137
|
fn(res)(myError);
|
|
122
138
|
});
|
|
123
139
|
});
|
|
@@ -145,7 +161,7 @@ describe(_.startCase(filename), function () {
|
|
|
145
161
|
|
|
146
162
|
expectStatus(res, 400);
|
|
147
163
|
expectResult(res, 'sendStatus: whatever', function () {
|
|
148
|
-
sinon.assert.calledWith(fakeLog, '
|
|
164
|
+
sinon.assert.calledWith(fakeLog, 'warn');
|
|
149
165
|
done();
|
|
150
166
|
});
|
|
151
167
|
fn(new Error('something'), res);
|
package/lib/services/composer.js
CHANGED
|
@@ -31,11 +31,12 @@ function resolveComponentReferences(data, locals, filter = referenceProperty) {
|
|
|
31
31
|
cmpt: referenceObject[referenceProperty]
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Errors with a .status property will be handled by lib/responses.js
|
|
35
|
+
// and logged with a level corresponding to the status code.
|
|
36
|
+
if (!error.status) {
|
|
37
|
+
log('error', `${error.message}`, logObj);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
log('error', `${error.message}`, logObj);
|
|
39
40
|
|
|
40
41
|
return bluebird.reject(error);
|
|
41
42
|
});
|
|
@@ -113,7 +113,7 @@ describe(_.startCase(filename), function () {
|
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
it('adds the status to the error message if it one is defined', function () {
|
|
116
|
+
it('adds the status to the error message if it one is defined, skips log', function () {
|
|
117
117
|
const data = {
|
|
118
118
|
a: {_ref: '/c/b'},
|
|
119
119
|
c: {d: {_ref: '/c/e'}}
|
|
@@ -127,9 +127,8 @@ describe(_.startCase(filename), function () {
|
|
|
127
127
|
components.get.withArgs('/c/m').returns(bluebird.reject(myError));
|
|
128
128
|
|
|
129
129
|
return fn(data).catch((error) => {
|
|
130
|
-
sinon.assert.calledOnce(logSpy);
|
|
131
130
|
expect(error.message).to.equal(errorMessage);
|
|
132
|
-
|
|
131
|
+
expect(error.status).to.equal(404);
|
|
133
132
|
});
|
|
134
133
|
});
|
|
135
134
|
});
|
|
@@ -80,6 +80,30 @@ function splitCascadingData(uri, data) {
|
|
|
80
80
|
return ops;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Adds user info onto each op object
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} locals
|
|
87
|
+
* @param {Object} locals.user
|
|
88
|
+
* @returns {Function}
|
|
89
|
+
*/
|
|
90
|
+
function addUserInfoToOp(locals) {
|
|
91
|
+
if (!locals) {
|
|
92
|
+
return _.identity;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { user } = locals;
|
|
96
|
+
|
|
97
|
+
function addUserProp(op) {
|
|
98
|
+
op.user = user;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (opsList) => {
|
|
102
|
+
opsList.forEach(addUserProp);
|
|
103
|
+
return opsList;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
83
107
|
/**
|
|
84
108
|
* Get a list of all the put operations needed to complete a cascading PUT
|
|
85
109
|
*
|
|
@@ -105,8 +129,10 @@ function getPutOperations(putFn, uri, data, locals) {
|
|
|
105
129
|
}
|
|
106
130
|
|
|
107
131
|
// run each through the normal put, which may or may not hit custom component logic
|
|
108
|
-
return bluebird
|
|
109
|
-
.
|
|
132
|
+
return bluebird
|
|
133
|
+
.map(cmptOrLayout, ({ key, value }) => putFn(key, value, locals))
|
|
134
|
+
.then(ops => _.filter(_.flattenDeep(ops), _.identity))
|
|
135
|
+
.then(addUserInfoToOp(locals));
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
/**
|
package/lib/services/pages.js
CHANGED
|
@@ -27,13 +27,17 @@ const _ = require('lodash'),
|
|
|
27
27
|
* @param {string} uri
|
|
28
28
|
* @param {{}} data
|
|
29
29
|
* @param {[]} ops
|
|
30
|
+
* @param {{}} locals
|
|
30
31
|
* @returns {array}
|
|
31
32
|
*/
|
|
32
|
-
function addOp(uri, data, ops) {
|
|
33
|
+
function addOp(uri, data, ops, locals = {}) {
|
|
34
|
+
const { user } = locals;
|
|
35
|
+
|
|
33
36
|
ops.push({
|
|
34
37
|
type: 'put',
|
|
35
38
|
key: uri,
|
|
36
|
-
value: JSON.stringify(data)
|
|
39
|
+
value: JSON.stringify(data),
|
|
40
|
+
user
|
|
37
41
|
});
|
|
38
42
|
|
|
39
43
|
return ops;
|
|
@@ -257,7 +261,7 @@ function publish(uri, data, locals) {
|
|
|
257
261
|
publishedMeta = meta;
|
|
258
262
|
|
|
259
263
|
// add page PUT operation last
|
|
260
|
-
return addOp(replaceVersion(uri, published), pageData, ops);
|
|
264
|
+
return addOp(replaceVersion(uri, published), pageData, ops, locals);
|
|
261
265
|
});
|
|
262
266
|
})
|
|
263
267
|
.then(applyBatch)
|
|
@@ -306,7 +310,7 @@ function create(uri, data, locals) {
|
|
|
306
310
|
return getPageClonePutOperations(pageData, locals)
|
|
307
311
|
.then(ops => {
|
|
308
312
|
pageData.layout = layoutReference;
|
|
309
|
-
return addOp(pageReference, pageData, ops);
|
|
313
|
+
return addOp(pageReference, pageData, ops, locals);
|
|
310
314
|
});
|
|
311
315
|
})
|
|
312
316
|
.then(applyBatch)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amphora",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.2",
|
|
4
4
|
"description": "An API mixin for Express that saves, publishes and composes data with the key-value store of your choice.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
"author": "New York Media",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"amphora-auth": "^2.0.0
|
|
21
|
-
"amphora-fs": "^2.0.0
|
|
20
|
+
"amphora-auth": "^2.0.0",
|
|
21
|
+
"amphora-fs": "^2.0.0",
|
|
22
22
|
"basic-auth": "^1.0.4",
|
|
23
23
|
"bluebird": "^3.5.1",
|
|
24
24
|
"body-parser": "^1.12",
|
|
25
25
|
"clay-log": "^1.2.0",
|
|
26
|
-
"clayutils": "^3.0.0
|
|
26
|
+
"clayutils": "^3.0.0",
|
|
27
27
|
"cuid": "^1.2.5",
|
|
28
28
|
"eventify": "^2.0.0",
|
|
29
29
|
"express": "^4.15.3",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"highland": "^2.11.1",
|
|
34
34
|
"ioredis": "^4.0.0",
|
|
35
35
|
"js-yaml": "^3.2.0",
|
|
36
|
-
"lodash": "^4.
|
|
36
|
+
"lodash": "^4.14.2",
|
|
37
37
|
"node-fetch": "^1.3.0",
|
|
38
38
|
"passport": "^0.3.2",
|
|
39
39
|
"passport-google-oauth": "^1.0.0",
|