Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"name": "node-api-seed",
"title": "Node Api Seed",
"version": "0.0.0",
"version": "0.0.1",
"engines": {
"node": ">=6.10.0"
},
"deploymentDate": "2016-11-09T19:15:04.309Z",
"description": "The seed for pretty much any api I write in node.js",
"main": "index.js",
Expand Down Expand Up @@ -74,8 +77,7 @@
"winston": "2.3.1",
"winston-daily-rotate-file": "1.4.6",
"winston-graylog2": "0.6.0",
"winston-loggly": "1.3.1",
"snyk": "^1.33.0"
"winston-loggly": "1.3.1"
},
"devDependencies": {
"chai": "4.0.1",
Expand All @@ -96,6 +98,7 @@
"proxyquire": "1.8.0",
"sinon": "2.3.2",
"sinon-chai": "2.10.0",
"snyk": "1.33.0",
"supertest": "3.0.0",
"swagger-ui": "3.0.13"
},
Expand Down
92 changes: 88 additions & 4 deletions src/crud/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ function addCreateRoute(router, crudMiddleware, maps) {
.describe(router.metadata.creationDescription || description(router.metadata));
return router;
}
addCreateRoute.getSteps = getSteps;
addCreateRoute.sendCreateResult = sendCreateResult;
addCreateRoute.description = description;
addCreateRoute.setStatusIfApplicable = setStatusIfApplicable;
addCreateRoute.setOwnerIfApplicable = setOwnerIfApplicable;
addCreateRoute.getFromReqObject = getFromReqObject;
addCreateRoute.getData = getData;

module.exports = addCreateRoute;

function getSteps(router, crudMiddleware, maps) {
Expand Down Expand Up @@ -100,21 +106,99 @@ function setStatusIfApplicable(metadata) {
if (!statuses || statuses.length <= 0) {
return next();
}
req.body.status = statuses[0].name;
const statusToSet = statuses[0];
req.body.status = statusToSet.name;
req.body.statusDate = moment.utc().toDate();
req.body.statusLog = [
{
status: req.body.status,
data: {
reason: 'Initial Status' //todo need to set this logically somehow
},
//we use 'addCreateRoute.' here to allow stubbing in the unit tests
data: addCreateRoute.getData(statusToSet.initialData, req),
statusDate: req.body.statusDate
}
];
return next();
};
}

function getData(rules, req) {
if (!rules) {
return;
}
//we use 'addCreateRoute.' here to allow stubbing in the unit tests
const fromReq = addCreateRoute.getFromReqObject(rules.fromReq, req);
return _.merge({}, rules.static, fromReq);
}

const defaultDisallowedSuffixList = ['password', 'passwordHash', 'passwordSalt'];
const defaultAllowedPrefixList = ['user', 'process', 'body', 'params', 'query'];
const maxDepth = 10;
function getFromReqObject(
map,
req,
depth = 0,
disallowedSuffixList = defaultDisallowedSuffixList,
allowedPrefixList = defaultAllowedPrefixList
) {
if (!map) {
return;
}
if (depth > maxDepth) {
throw new Error(
util.format(
'Circular reference detected in map object after maximum depth (%s) reached. Partial map\n%j\n',
maxDepth,
util.inspect(map, true, maxDepth)
)
);
}
const data = {};
Object.keys(map).forEach(function(key) {
const value = map[key];
if (_.isArray(value)) {
ensureMapIsString(value[0]);
if (value.length > 2) {
throw new Error(
util.format('Too many items in array, should be at most 2. %j', value)
);
}
data[key] = getValue(req, value[0], value[1], disallowedSuffixList, allowedPrefixList);
return;
}
if (_.isObject(value)) {
data[key] = getFromReqObject(
value,
req,
depth + 1,
disallowedSuffixList,
allowedPrefixList
);
return;
}
ensureMapIsString(value);
data[key] = getValue(req, value, undefined, disallowedSuffixList, allowedPrefixList);
});
return data;
}

function getValue(req, map, defaultValue, disallowedSuffixList, allowedPrefixList) {
const disallowed = disallowedSuffixList.find(suffix => map.endsWith(suffix));
if (disallowed) {
throw new Error('Map is not allowed to end with ' + disallowed);
}
const allowed = allowedPrefixList.find(prefix => map.startsWith(prefix));
if (!allowed) {
throw new Error(util.format('Map must start with one of %j', allowedPrefixList));
}
return _.get(req, map, defaultValue);
}

function ensureMapIsString(map) {
if (!_.isString(map)) {
throw new Error(util.format('Invalid map value, must be a string : \n%j\n', map));
}
}

function setOwnerIfApplicable(metadata) {
return function _setOwnerIfApplicable(req, res, next) {
let ownership = metadata.schemas.core.ownership;
Expand Down
6 changes: 3 additions & 3 deletions src/metadata/hydrate-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ function addStatusInfo(schema) {
properties: {
status: schema.properties.status,
statusDate: schema.properties.statusDate,
data: schema.updateStatusSchema
data: schema.updateStatusSchema //todo anyof?
},
required: ['status', 'statusDate', 'data'],
required: ['status', 'statusDate', 'data'], //todo - data check schema anyof?
additionalProperties: false
},
additionalItems: false
Expand Down Expand Up @@ -84,7 +84,7 @@ function addOwnerInfo(schema) {
type: ['object', 'string'] //todo?
}
},
required: ['owner', 'ownerDate', 'data'],
required: ['owner', 'ownerDate', 'data'], //todo - data check schema anyof?
additionalProperties: false
},
additionalItems: false
Expand Down
7 changes: 6 additions & 1 deletion src/routes/users/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
"statuses": [
{
"name": "active",
"description": "Default status, shows that the user is active and can login"
"description": "Default status, shows that the user is active and can login",
"initialData":{
"static":{
"reason":"testing"
}
}
},
{
"name": "inactive",
Expand Down
56 changes: 56 additions & 0 deletions test/@util/request-mocking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';
const httpMocks = require('node-mocks-http');
const events = require('events');

module.exports = {
mockRequest,
shouldNotCallNext,
shouldCallNext,
shouldNotReturnResponse
};

function mockRequest(middlewareOrRouter, reqOptions, responseCallback, nextCallback) {
const req = httpMocks.createRequest(reqOptions);
const res = httpMocks.createResponse({
eventEmitter: events.EventEmitter
});
res.on('end', function() {
let resToReturn;
try {
resToReturn = {
statusCode: res._getStatusCode(),
body: JSON.parse(res._getData()),
headers: res._getHeaders(),
raw: res
};
} catch (err) {
return responseCallback(err);
}
responseCallback(null, resToReturn);
});
middlewareOrRouter(req, res, nextCallback);
}

function shouldNotCallNext(done) {
return function next(err) {
if (err) {
return done(err);
}
return done(new Error('Next should not have been called'));
};
}

function shouldCallNext(done) {
return function next(err) {
if (err) {
return done(err);
}
return done();
};
}

function shouldNotReturnResponse(done) {
return function resComplete() {
done(new Error('res.end should not have been called'));
};
}
Loading