jsonapi-server
Advanced tools
Comparing version 0.16.0 to 1.0.0
@@ -53,1 +53,10 @@ 2015-06-29 - Initial release | ||
2015-11-25 - v0.16.0 | ||
2015-12-03 - Top level jsonapi blocks | ||
2015-12-05 - Tooling improvements | ||
2015-12-07 - Efficient inclusions | ||
2015-12-08 - Pagination support | ||
2015-12-10 - CPU profiling | ||
2015-12-10 - Move to lodash | ||
2015-12-10 - Use filter[] instead of relationships[] | ||
2015-12-10 - Inclusion bug fixes | ||
2015-12-10 - v1.0.0 |
@@ -16,3 +16,5 @@ | ||
- jsonApi:include | ||
- jsonApi:filter | ||
- jsonApi:errors | ||
- jsonApi:requestCounter | ||
@@ -19,0 +21,0 @@ |
@@ -177,3 +177,3 @@ ### Foreign Key Relations | ||
// get full details of all linked resources (perform a search against the foreign key) | ||
related: "http://localhost:16006/rest/articles/?relationships[comments]=6b017640-827c-4d50-8dcc-79d766abb408" | ||
related: "http://localhost:16006/rest/articles/?filter[comments]=6b017640-827c-4d50-8dcc-79d766abb408" | ||
} | ||
@@ -180,0 +180,0 @@ } |
@@ -60,1 +60,10 @@ ### Post Processing | ||
http://localhost:16006/rest/articles?sort=+title | ||
#### Pagination | ||
Use `page[limit]=50` to limit the number of resources in a search request to 50. | ||
Use `page[offset]=10` to chose which resulting resource should start the result set. | ||
To fetch resources 100-149: | ||
http://localhost:16006/rest/articles?page[offset]=100&page[limit]=50 |
@@ -76,3 +76,3 @@ var jsonApi = require("../../."); | ||
tags: [ | ||
{ type: "tags", id: "7541a4de-4986-4597-81b9-cf31b6762486" } | ||
{ type: "tags", id: "8d196606-134c-4504-a93a-0d372f78d6c5" } | ||
], | ||
@@ -79,0 +79,0 @@ photos: [ |
@@ -16,2 +16,7 @@ var jsonApi = require("../../."); | ||
as: "tags" | ||
}), | ||
parent: jsonApi.Joi.one("tags"), | ||
children: jsonApi.Joi.belongsToMany({ | ||
resource: "tags", | ||
as: "parent" | ||
}) | ||
@@ -23,3 +28,4 @@ }, | ||
type: "tags", | ||
name: "live" | ||
name: "live", | ||
parent: { type: "tags", id: "2a3bdea4-a889-480d-b886-104498c86f69" } | ||
}, | ||
@@ -29,3 +35,4 @@ { | ||
type: "tags", | ||
name: "staging" | ||
name: "staging", | ||
parent: { type: "tags", id: "6ec62f6d-9f82-40c5-b4f4-279ed1765492" } | ||
}, | ||
@@ -35,5 +42,18 @@ { | ||
type: "tags", | ||
name: "needs-work" | ||
name: "building", | ||
parent: { type: "tags", id: "68538177-7a62-4752-bc4e-8f971d253b42" } | ||
}, | ||
{ | ||
id: "68538177-7a62-4752-bc4e-8f971d253b42", | ||
type: "tags", | ||
name: "development", | ||
parent: { type: "tags", id: "8d196606-134c-4504-a93a-0d372f78d6c5" } | ||
}, | ||
{ | ||
id: "8d196606-134c-4504-a93a-0d372f78d6c5", | ||
type: "tags", | ||
name: "planning", | ||
parent: null | ||
} | ||
] | ||
}); |
@@ -11,5 +11,7 @@ "use strict"; | ||
include: require("debug")("jsonApi:include"), | ||
filter: require("debug")("jsonApi:filter"), | ||
validationInput: require("debug")("jsonApi:validation:input"), | ||
validationOutput: require("debug")("jsonApi:validation:output"), | ||
errors: require("debug")("jsonApi:errors") | ||
errors: require("debug")("jsonApi:errors"), | ||
requestCounter: require("debug")("jsonApi:requestCounter") | ||
}; |
@@ -14,50 +14,39 @@ "use strict"; | ||
handlerEnforcer._search = function(handlers) { | ||
var original = handlers.search; | ||
return function(request, callback) { | ||
original.call(handlers, request, function(err, resources) { | ||
debug.handler.search(JSON.stringify(request.params), JSON.stringify(err), JSON.stringify(resources)); | ||
return callback(err, resources); | ||
handlerEnforcer._wrapHandler = function(handlers, operation, outCount) { | ||
var original = handlers[operation]; | ||
return function() { | ||
var argsIn = Array.prototype.slice.call(arguments); | ||
var requestParams = argsIn[0].params; | ||
var callback = argsIn.pop(); | ||
argsIn.push(function() { | ||
var argsOut = Array.prototype.slice.call(arguments); | ||
argsOut = argsOut.slice(0, outCount); | ||
while (argsOut.length < outCount) { | ||
argsOut.push(null); | ||
} | ||
debug.handler[operation](JSON.stringify(requestParams), JSON.stringify(argsOut)); | ||
return callback.apply(null, argsOut); | ||
}); | ||
original.apply(handlers, argsIn); | ||
}; | ||
}; | ||
handlerEnforcer._search = function(handlers) { | ||
return handlerEnforcer._wrapHandler(handlers, "search", 3); | ||
}; | ||
handlerEnforcer._find = function(handlers) { | ||
var original = handlers.find; | ||
return function(request, callback) { | ||
original.call(handlers, request, function(err, resource) { | ||
debug.handler.find(JSON.stringify(request.params), JSON.stringify(err), JSON.stringify(resource)); | ||
return callback(err, resource); | ||
}); | ||
}; | ||
return handlerEnforcer._wrapHandler(handlers, "find", 2); | ||
}; | ||
handlerEnforcer._create = function(handlers) { | ||
var original = handlers.create; | ||
return function(request, newResource, callback) { | ||
original.call(handlers, request, newResource, function(err, handledResource) { | ||
debug.handler.create(JSON.stringify(request.params), JSON.stringify(newResource), JSON.stringify(err), JSON.stringify(handledResource)); | ||
return callback(err, handledResource); | ||
}); | ||
}; | ||
return handlerEnforcer._wrapHandler(handlers, "create", 2); | ||
}; | ||
handlerEnforcer._update = function(handlers) { | ||
var original = handlers.update; | ||
return function(request, partialResource, callback) { | ||
original.call(handlers, request, partialResource, function(err, modifiedResource) { | ||
debug.handler.update(JSON.stringify(request.params), JSON.stringify(partialResource), JSON.stringify(err), JSON.stringify(modifiedResource)); | ||
return callback(err, modifiedResource); | ||
}); | ||
}; | ||
return handlerEnforcer._wrapHandler(handlers, "update", 2); | ||
}; | ||
handlerEnforcer._delete = function(handlers) { | ||
var original = handlers.delete; | ||
return function(request, callback) { | ||
original.call(handlers, request, function(err) { | ||
debug.handler.delete(JSON.stringify(request.params), JSON.stringify(err)); | ||
return callback(err); | ||
}); | ||
}; | ||
return handlerEnforcer._wrapHandler(handlers, "delete", 1); | ||
}; |
@@ -6,3 +6,6 @@ "use strict"; | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign"), | ||
pick: require("lodash.pick") | ||
}; | ||
var ourJoi = require("./ourJoi.js"); | ||
@@ -12,2 +15,3 @@ var router = require("./router.js"); | ||
var handlerEnforcer = require("./handlerEnforcer.js"); | ||
var pagination = require("./pagination.js"); | ||
var routes = require("./routes"); | ||
@@ -68,3 +72,3 @@ var url = require("url"); | ||
resourceConfig.searchParams = _.extend({ | ||
resourceConfig.searchParams = _.assign({ | ||
type: ourJoi.Joi.any().required().valid(resourceConfig.resource) | ||
@@ -84,9 +88,6 @@ .description("Always \"" + resourceConfig.resource + "\"") | ||
.description("An attribute to include") | ||
.example("title"), | ||
relationships: ourJoi.Joi.any() | ||
.description("An attribute to include") | ||
.example("title") | ||
}, resourceConfig.searchParams); | ||
}, resourceConfig.searchParams, pagination.joiPageDefinition); | ||
resourceConfig.attributes = _.extend({ | ||
resourceConfig.attributes = _.assign({ | ||
id: ourJoi.Joi.string().required() | ||
@@ -93,0 +94,0 @@ .description("Unique resource identifier") |
"use strict"; | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign") | ||
}; | ||
@@ -25,25 +27,14 @@ var MemoryStore = module.exports = function MemoryStore() { | ||
/** | ||
Search for a list of resources, give a resource type. | ||
Search for a list of resources, given a resource type. | ||
*/ | ||
MemoryStore.prototype.search = function(request, callback) { | ||
// If a relationships param is passed in, filter against those relations | ||
if (request.params.relationships) { | ||
var mustMatch = request.params.relationships; | ||
var matches = resources[request.params.type].filter(function(anyResource) { | ||
var match = true; | ||
Object.keys(mustMatch).forEach(function(i) { | ||
var fKeys = anyResource[i]; | ||
if (!(fKeys instanceof Array)) fKeys = [ fKeys ]; | ||
fKeys = fKeys.map(function(j) { return j.id; }); | ||
if (fKeys.indexOf(mustMatch[i]) === -1) { | ||
match = false; | ||
} | ||
}); | ||
return match; | ||
}); | ||
return callback(null, matches); | ||
var self = this; | ||
var results = [].concat(resources[request.params.type]); | ||
self._sortList(request, results); | ||
var resultCount = results.length; | ||
if (request.params.page) { | ||
results = results.slice(request.params.page.offset, request.params.page.offset + request.params.page.limit); | ||
} | ||
// No specific search params are supported, so return ALL resources of the requested type | ||
return callback(null, resources[request.params.type]); | ||
return callback(null, results, resultCount); | ||
}; | ||
@@ -111,3 +102,3 @@ | ||
// Merge the partialResource over the original | ||
theResource = _.extend(theResource, partialResource); | ||
theResource = _.assign(theResource, partialResource); | ||
@@ -121,1 +112,26 @@ // Push the newly updated resource back into the in-memory store | ||
}; | ||
/** | ||
Internal helper function to sort data | ||
*/ | ||
MemoryStore.prototype._sortList = function(request, list) { | ||
var attribute = request.params.sort; | ||
if (!attribute) return; | ||
var ascending = 1; | ||
attribute = ("" + attribute); | ||
if (attribute[0] === "-") { | ||
ascending = -1; | ||
attribute = attribute.substring(1, attribute.length); | ||
} | ||
list.sort(function(a, b) { | ||
if (typeof a[attribute] === "string") { | ||
return a[attribute].localeCompare(b[attribute]) * ascending; | ||
} else if (typeof a[attribute] === "number") { | ||
return (a[attribute] - b[attribute]) * ascending; | ||
} else { | ||
return 0; | ||
} | ||
}); | ||
}; |
@@ -5,3 +5,3 @@ "use strict"; | ||
var jsonApi = require(".."); | ||
var _ = require("underscore"); | ||
var debug = require("./debugging.js"); | ||
var externalRequest = require("request").defaults({ | ||
@@ -41,6 +41,18 @@ pool: { maxSockets: Infinity } | ||
var resourcesToFetch = dataItems.map(function(dataItem) { | ||
return jsonApi._apiConfig.pathPrefix + dataItem.type + "/" + dataItem.id; | ||
var resourcesToFetch = dataItems.reduce(function(map, dataItem) { | ||
map[dataItem.type] = map[dataItem.type] || [ ]; | ||
map[dataItem.type].push(dataItem.id); | ||
return map; | ||
}, { }); | ||
resourcesToFetch = Object.keys(resourcesToFetch).map(function(type) { | ||
var ids = resourcesToFetch[type]; | ||
var urlJoiner = "&filter[id]="; | ||
ids = urlJoiner + ids.join(urlJoiner); | ||
return jsonApi._apiConfig.pathPrefix + type + "/?" + ids; | ||
}); | ||
async.map(resourcesToFetch, function(related, done) { | ||
debug.include(related); | ||
externalRequest({ | ||
@@ -79,12 +91,10 @@ method: "GET", | ||
items.forEach(function(item) { | ||
Object.keys(schema).map(function(i) { | ||
return _.extend({ name: i }, schema[i]); | ||
}).filter(function(schemaProperty) { | ||
var settings = schemaProperty._settings; | ||
return settings && settings.__as; | ||
}).forEach(function(schemaProperty) { | ||
item[schemaProperty.name] = undefined; | ||
}); | ||
for (var i in schema) { | ||
var settings = schema[i]._settings; | ||
if (settings && settings.__as) { | ||
item[i] = undefined; | ||
} | ||
} | ||
}); | ||
return callback(); | ||
}; |
"use strict"; | ||
var filter = module.exports = { }; | ||
var _ = { | ||
assign: require("lodash.assign") | ||
}; | ||
var debug = require("../debugging.js"); | ||
filter.action = function(request, response, callback) { | ||
var allFilters = request.params.filter; | ||
var allFilters = _.assign({ }, request.params.filter); | ||
if (!allFilters) return callback(); | ||
@@ -29,2 +34,3 @@ | ||
if (!filter._filterKeepObject(response.data[j], filters)) { | ||
debug.filter("removed", filters, JSON.stringify(response.data[j].attributes)); | ||
response.data.splice(j, 1); | ||
@@ -36,2 +42,3 @@ j--; | ||
if (!filter._filterKeepObject(response.data, filters)) { | ||
debug.filter("removed", filters, JSON.stringify(response.data.attributes)); | ||
response.data = null; | ||
@@ -61,13 +68,48 @@ } | ||
filter._filterKeepObject = function(someObject, filters) { | ||
for (var k in filters) { | ||
var whitelist = filters[k].split(","); | ||
var propertyText = (someObject.attributes[k] || ""); | ||
var matchOR = false; | ||
for (var j = 0; j < whitelist.length; j++) { | ||
var textToMatch = whitelist[j]; | ||
if (filter._filterMatches(textToMatch, propertyText)) matchOR = true; | ||
for (var filterName in filters) { | ||
var whitelist = filters[filterName].split(","); | ||
if (someObject.attributes.hasOwnProperty(filterName) || (filterName === "id")) { | ||
var attributeValue = someObject.attributes[filterName] || ""; | ||
if (filterName === "id") attributeValue = someObject.id; | ||
var attributeMatches = filter._attributesMatchesOR(attributeValue, whitelist); | ||
if (!attributeMatches) return false; | ||
} | ||
if (!matchOR) return false; | ||
if (someObject.relationships.hasOwnProperty(filterName)) { | ||
var relationships = someObject.relationships[filterName] || ""; | ||
var relationshipMatches = filter._relationshipMatchesOR(relationships, whitelist); | ||
if (!relationshipMatches) return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
filter._attributesMatchesOR = function(attributeValue, whitelist) { | ||
var matchOR = false; | ||
whitelist.forEach(function(textToMatch) { | ||
if (filter._filterMatches(textToMatch, attributeValue)) { | ||
matchOR = true; | ||
} | ||
}); | ||
return matchOR; | ||
}; | ||
filter._relationshipMatchesOR = function(relationships, whitelist) { | ||
var matchOR = false; | ||
var data = relationships.data; | ||
if (!data) return false; | ||
if (!(data instanceof Array)) data = [ data ]; | ||
data = data.map(function(relation) { | ||
return relation.id; | ||
}); | ||
whitelist.forEach(function(textToMatch) { | ||
if (data.indexOf(textToMatch) !== -1) { | ||
matchOR = true; | ||
} | ||
}); | ||
return matchOR; | ||
}; |
@@ -5,3 +5,5 @@ "use strict"; | ||
var jsonApi = require("../jsonApi.js"); | ||
var _ = require("underscore"); | ||
var _ = { | ||
unique: require("lodash.uniq") | ||
}; | ||
var externalRequest = require("request").defaults({ | ||
@@ -31,3 +33,3 @@ pool: { maxSockets: Infinity } | ||
response.included = includePP._getDataItemsFromTree(includeTree); | ||
response.included = _.uniq(response.included, false, function(someItem) { | ||
response.included = _.unique(response.included, false, function(someItem) { | ||
return someItem.type + "~~" + someItem.id; | ||
@@ -73,2 +75,8 @@ }); | ||
filter = filter[first] || { }; | ||
if (filter instanceof Array) { | ||
filter = filter.filter(function(i) { | ||
return i instanceof Object; | ||
}).pop(); | ||
} | ||
if (!node[first]) { | ||
@@ -81,5 +89,7 @@ node[first] = { | ||
for (var i in filter) { | ||
if (!(typeof filter[i] === "string" || (filter[i] instanceof Array))) continue; | ||
node[first]._filter.push("filter[" + i + "]=" + filter[i]); | ||
if (!((filter instanceof Array) && (filter.length === 0))) { | ||
for (var i in filter) { | ||
if (!(typeof filter[i] === "string" || (filter[i] instanceof Array))) continue; | ||
node[first]._filter.push("filter[" + i + "]=" + filter[i]); | ||
} | ||
} | ||
@@ -118,31 +128,72 @@ } | ||
var resourcesToFetch = includeTree._dataItems.map(function(dataItem) { | ||
var map = { | ||
primary: { }, | ||
foreign: { } | ||
}; | ||
includeTree._dataItems.forEach(function(dataItem) { | ||
if (!dataItem) return [ ]; | ||
return Object.keys(dataItem.relationships || { }).filter(function(keyName) { | ||
return (keyName[0] !== "_") && (includes.indexOf(keyName) !== -1); | ||
}).map(function(keyName) { | ||
var url = dataItem.relationships[keyName].links.related; | ||
if (url.indexOf("?") === -1) url += "?"; | ||
if (includeTree[keyName]._filter) { | ||
url += "&" + includeTree[keyName]._filter.join("&"); | ||
}).forEach(function(relation) { | ||
var someRelation = dataItem.relationships[relation]; | ||
if (someRelation.meta.relation === "primary") { | ||
var relationItems = someRelation.data; | ||
if (!relationItems) return; | ||
if (!(relationItems instanceof Array)) relationItems = [ relationItems ]; | ||
relationItems.forEach(function(relationItem) { | ||
var key = relationItem.type + "~~" + relation + "~~" + relation; | ||
map.primary[key] = map.primary[key] || [ ]; | ||
map.primary[key].push(relationItem.id); | ||
}); | ||
} | ||
return keyName + "~~" + url; | ||
if (someRelation.meta.relation === "foreign") { | ||
var key = someRelation.meta.as + "~~" + someRelation.meta.belongsTo + "~~" + relation; | ||
map.foreign[key] = map.foreign[key] || [ ]; | ||
map.foreign[key].push(dataItem.id); | ||
} | ||
}); | ||
}); | ||
resourcesToFetch = [].concat.apply([], resourcesToFetch); | ||
resourcesToFetch = _.unique(resourcesToFetch); | ||
var resourcesToFetch = []; | ||
Object.keys(map.primary).forEach(function(relation) { | ||
var ids = _.unique(map.primary[relation]); | ||
var parts = relation.split("~~"); | ||
var urlJoiner = "&filter[id]="; | ||
ids = urlJoiner + ids.join(urlJoiner); | ||
if (includeTree[parts[1]]._filter) { | ||
ids += "&" + includeTree[parts[1]]._filter.join("&"); | ||
} | ||
resourcesToFetch.push({ | ||
url: jsonApi._apiConfig.pathPrefix + parts[0] + "/?" + ids, | ||
as: relation | ||
}); | ||
}); | ||
Object.keys(map.foreign).forEach(function(relation) { | ||
var ids = _.unique(map.foreign[relation]); | ||
var parts = relation.split("~~"); | ||
var urlJoiner = "&filter[" + parts[0] + "]="; | ||
ids = urlJoiner + ids.join(urlJoiner); | ||
if (includeTree[parts[2]]._filter) { | ||
ids += "&" + includeTree[parts[2]]._filter.join("&"); | ||
} | ||
resourcesToFetch.push({ | ||
url: jsonApi._apiConfig.pathPrefix + parts[1] + "/?" + ids, | ||
as: relation | ||
}); | ||
}); | ||
async.map(resourcesToFetch, function(related, done) { | ||
var parts = related.split("~~"); | ||
var type = parts[0]; | ||
var link = parts[1]; | ||
var parts = related.as.split("~~"); | ||
debug.include(related); | ||
debug.include(link); | ||
externalRequest({ | ||
method: "GET", | ||
uri: link, | ||
uri: related.url, | ||
headers: request.safeHeaders | ||
}, function(err, res, json) { | ||
// console.log(err, json) | ||
if (err || !json) { | ||
@@ -167,3 +218,3 @@ return done(null); | ||
if (!(data instanceof Array)) data = [ data ]; | ||
includeTree[type]._dataItems = includeTree[type]._dataItems.concat(data); | ||
includeTree[parts[2]]._dataItems = includeTree[parts[2]]._dataItems.concat(data); | ||
return done(); | ||
@@ -170,0 +221,0 @@ }); |
"use strict"; | ||
var responseHelper = module.exports = { }; | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign"), | ||
pick: require("lodash.pick") | ||
}; | ||
var async = require("async"); | ||
var pagination = require("./pagination.js"); | ||
var Joi = require("joi"); | ||
@@ -139,4 +143,4 @@ var debug = require("./debugging.js"); | ||
// get full details of all linked resources | ||
// /rest/bookings/?relationships[customer]=26aa8a92-2845-4e40-999f-1fa006ec8c63 | ||
link.links.related = responseHelper._baseUrl + relatedResource + "/?relationships[" + schemaProperty._settings.__as + "]=" + item.id; | ||
// /rest/bookings/?filter[customer]=26aa8a92-2845-4e40-999f-1fa006ec8c63 | ||
link.links.related = responseHelper._baseUrl + relatedResource + "/?filter[" + schemaProperty._settings.__as + "]=" + item.id; | ||
if (!item[linkProperty]) { | ||
@@ -158,6 +162,9 @@ link.data = undefined; | ||
responseHelper.generateError = function(request, err) { | ||
debug.errors(err); | ||
debug.errors(request.route.verb, request.route.combined, JSON.stringify(err)); | ||
if (!(err instanceof Array)) err = [ err ]; | ||
var errorResponse = { | ||
jsonapi: { | ||
version: "1.0" | ||
}, | ||
meta: responseHelper._generateMeta(request), | ||
@@ -180,8 +187,11 @@ links: { | ||
responseHelper._generateResponse = function(request, resourceConfig, sanitisedData) { | ||
responseHelper._generateResponse = function(request, resourceConfig, sanitisedData, handlerTotal) { | ||
return { | ||
meta: responseHelper._generateMeta(request), | ||
links: { | ||
jsonapi: { | ||
version: "1.0" | ||
}, | ||
meta: responseHelper._generateMeta(request, handlerTotal), | ||
links: _.assign({ | ||
self: responseHelper._baseUrl + request.route.path + (request.route.query ? ("?" + request.route.query) : "") | ||
}, | ||
}, pagination.generatePageLinks(request, handlerTotal)), | ||
data: sanitisedData | ||
@@ -191,4 +201,10 @@ }; | ||
responseHelper._generateMeta = function() { | ||
return responseHelper._metadata; | ||
responseHelper._generateMeta = function(request, handlerTotal) { | ||
var meta = _.assign({ }, responseHelper._metadata); | ||
if (handlerTotal) { | ||
meta.page = pagination.generateMetaSummary(request, handlerTotal); | ||
} | ||
return meta; | ||
}; |
"use strict"; | ||
var router = module.exports = { }; | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign"), | ||
omit: require("lodash.omit") | ||
}; | ||
var express = require("express"); | ||
@@ -11,2 +14,3 @@ var app = express(); | ||
var jsonApi = require("./jsonApi.js"); | ||
var debug = require("./debugging.js"); | ||
var url = require("url"); | ||
@@ -52,9 +56,8 @@ | ||
// var requestId = 0; | ||
// app.route("*").all(function(req, res, next) { | ||
// req.query.requestId = requestId; | ||
// console.log(requestId, "===", req.url); | ||
// requestId++; | ||
// next(); | ||
// }); | ||
var requestId = 0; | ||
app.route("*").all(function(req, res, next) { | ||
debug.requestCounter(requestId++, req.url); | ||
if (requestId > 1000) requestId = 0; | ||
next(); | ||
}); | ||
@@ -124,3 +127,3 @@ router.listen = function(port) { | ||
return { | ||
params: _.extend(req.params, req.body, req.query), | ||
params: _.assign(req.params, req.body, req.query), | ||
headers: req.headers, | ||
@@ -130,2 +133,3 @@ safeHeaders: _.omit(req.headers, headersToRemove), | ||
route: { | ||
verb: req.method, | ||
host: req.headers.host, | ||
@@ -137,5 +141,5 @@ base: jsonApi._apiConfig.base, | ||
protocol: jsonApi._apiConfig.protocol, | ||
hostname: req.headers.host, | ||
pathname: req.url | ||
}) | ||
hostname: jsonApi._apiConfig.hostname, | ||
port: jsonApi._apiConfig.port | ||
}) + req.url | ||
} | ||
@@ -142,0 +146,0 @@ }; |
@@ -56,3 +56,3 @@ "use strict"; | ||
}, | ||
function(results, callback) { | ||
function(results, pageData, callback) { | ||
searchResults = results.map(function(result) { | ||
@@ -59,0 +59,0 @@ return { |
@@ -5,3 +5,5 @@ "use strict"; | ||
var async = require("async"); | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign") | ||
}; | ||
var uuid = require("node-uuid"); | ||
@@ -35,3 +37,3 @@ var helper = require("./helper.js"); | ||
var theirs = request.params.data; | ||
theirResource = _.extend({ | ||
theirResource = _.assign({ | ||
id: uuid.v4(), | ||
@@ -38,0 +40,0 @@ type: request.params.type |
@@ -7,2 +7,3 @@ "use strict"; | ||
var router = require("../router.js"); | ||
var pagination = require("../pagination.js"); | ||
var postProcess = require("../postProcess.js"); | ||
@@ -19,2 +20,3 @@ var responseHelper = require("../responseHelper.js"); | ||
var response; | ||
var paginationInfo; | ||
@@ -28,34 +30,7 @@ async.waterfall([ | ||
}, | ||
function(callback) { | ||
if (!request.params.relationships) return callback(); | ||
var target = Object.keys(request.params.relationships)[0]; | ||
var relation = resourceConfig.attributes[target]; | ||
if (!relation || !relation._settings || !(relation._settings.__one || relation._settings.__many)) { | ||
return callback({ | ||
status: "403", | ||
code: "EFORBIDDEN", | ||
title: "Request validation failed", | ||
detail: "Requested relation \"" + target + "\" does not exist on " + request.params.type | ||
}); | ||
} | ||
if (relation._settings.__as) { | ||
return callback({ | ||
status: "403", | ||
code: "EFORBIDDEN", | ||
title: "Request validation failed", | ||
detail: "Requested relation \"" + target + "\" is a foreign reference and does not exist on " + request.params.type | ||
}); | ||
} | ||
return callback(); | ||
}, | ||
function validateFilterParams(callback) { | ||
var allFilters = request.params.filter; | ||
if (!allFilters) return callback(); | ||
if (!request.params.filter) return callback(); | ||
var filters = { }; | ||
for (var i in allFilters) { | ||
for (var i in request.params.filter) { | ||
if (request.params.filter[i] instanceof Object) continue; | ||
if (!request.resourceConfig.attributes[i]) { | ||
@@ -69,16 +44,28 @@ return callback({ | ||
} | ||
if (allFilters[i] instanceof Array) { | ||
allFilters[i] = allFilters[i].join(","); | ||
var relationSettings = request.resourceConfig.attributes[i]._settings; | ||
if (relationSettings && relationSettings.__as) { | ||
return callback({ | ||
status: "403", | ||
code: "EFORBIDDEN", | ||
title: "Request validation failed", | ||
detail: "Requested relation \"" + i + "\" is a foreign reference and does not exist on " + request.params.type | ||
}); | ||
} | ||
filters[i] = allFilters[i]; | ||
} | ||
request.params.filter = filters; | ||
return callback(); | ||
}, | ||
function validatePaginationParams(callback) { | ||
pagination.validatePaginationParams(request); | ||
return callback(); | ||
}, | ||
function(callback) { | ||
resourceConfig.handlers.search(request, callback); | ||
}, | ||
function(results, callback) { | ||
searchResults = results; | ||
function enforcePagination(results, pageInfo, callback) { | ||
searchResults = pagination.enforcePagination(request, results); | ||
paginationInfo = pageInfo; | ||
return callback(); | ||
}, | ||
function(callback) { | ||
postProcess.fetchForeignKeys(request, searchResults, resourceConfig.attributes, callback); | ||
@@ -90,3 +77,3 @@ }, | ||
function(sanitisedData, callback) { | ||
response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); | ||
response = responseHelper._generateResponse(request, resourceConfig, sanitisedData, paginationInfo); | ||
response.included = [ ]; | ||
@@ -93,0 +80,0 @@ postProcess.handle(request, response, callback); |
@@ -5,3 +5,6 @@ "use strict"; | ||
var async = require("async"); | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign"), | ||
pick: require("lodash.pick") | ||
}; | ||
var helper = require("./helper.js"); | ||
@@ -34,3 +37,3 @@ var router = require("../router.js"); | ||
var theirs = request.params.data; | ||
theirResource = _.extend({ | ||
theirResource = _.assign({ | ||
id: request.params.id, | ||
@@ -37,0 +40,0 @@ type: request.params.type |
@@ -5,3 +5,5 @@ "use strict"; | ||
var async = require("async"); | ||
var _ = require("underscore"); | ||
var _ = { | ||
assign: require("lodash.assign") | ||
}; | ||
var helper = require("./helper.js"); | ||
@@ -34,3 +36,3 @@ var router = require("../router.js"); | ||
var theirs = request.params.data; | ||
theirResource = _.extend({ | ||
theirResource = _.assign({ | ||
id: request.params.id, | ||
@@ -37,0 +39,0 @@ type: request.params.type |
@@ -69,2 +69,9 @@ "use strict"; | ||
type: "string" | ||
}, | ||
page: { | ||
name: "page", | ||
in: "query", | ||
description: "Pagination namespace", | ||
required: false, | ||
type: "string" | ||
} | ||
@@ -71,0 +78,0 @@ }, |
@@ -119,4 +119,13 @@ "use strict"; | ||
type: "object", | ||
required: [ "meta, links" ], | ||
required: [ "jsonapi", "meta, links" ], | ||
properties: { | ||
jsonapi: { | ||
type: "object", | ||
required: [ "version" ], | ||
properties: { | ||
version: { | ||
type: "string" | ||
} | ||
} | ||
}, | ||
meta: { | ||
@@ -131,2 +140,14 @@ type: "object" | ||
type: "string" | ||
}, | ||
first: { | ||
type: "string" | ||
}, | ||
last: { | ||
type: "string" | ||
}, | ||
next: { | ||
type: "string" | ||
}, | ||
prev: { | ||
type: "string" | ||
} | ||
@@ -133,0 +154,0 @@ } |
@@ -142,3 +142,13 @@ "use strict"; | ||
type: "object", | ||
required: [ "jsonapi", "meta", "links", "errors" ], | ||
properties: { | ||
jsonapi: { | ||
type: "object", | ||
required: [ "version" ], | ||
properties: { | ||
version: { | ||
type: "string" | ||
} | ||
} | ||
}, | ||
meta: { | ||
@@ -145,0 +155,0 @@ type: "object" |
{ | ||
"name": "jsonapi-server", | ||
"version": "0.16.0", | ||
"version": "1.0.0", | ||
"description": "A fully featured NodeJS sever implementation of json:api. You provide the resources, we provide the api.", | ||
@@ -28,5 +28,8 @@ "keywords": [ | ||
"joi": "6.7.1", | ||
"lodash.assign": "3.2.0", | ||
"lodash.omit": "3.1.0", | ||
"lodash.pick": "3.1.0", | ||
"lodash.uniq": "3.2.2", | ||
"node-uuid": "1.4.3", | ||
"request": "2.63.0", | ||
"underscore": "1.8.3" | ||
"request": "2.63.0" | ||
}, | ||
@@ -40,12 +43,14 @@ "devDependencies": { | ||
"plato": "1.5.0", | ||
"mocha-performance": "0.1.0" | ||
"mocha-performance": "0.1.0", | ||
"v8-profiler": "5.5.0", | ||
"node-inspector": "0.12.5" | ||
}, | ||
"scripts": { | ||
"test": "./node_modules/mocha/bin/mocha -R spec ./test/*.js", | ||
"test": "node ./node_modules/mocha/bin/mocha -R spec ./test/*.js", | ||
"start": "node example/server.js", | ||
"coveralls": "./node_modules/mocha/bin/mocha --require blanket --reporter mocha-lcov-reporter ./test/*.js | ./node_modules/coveralls/bin/coveralls.js", | ||
"coverage": "./node_modules/mocha/bin/mocha --require blanket --reporter html-cov ./test/*.js > coverage.html", | ||
"complexity": "./node_modules/plato/bin/plato -r -d complexity lib", | ||
"coveralls": "node ./node_modules/mocha/bin/mocha --require blanket --reporter mocha-lcov-reporter ./test/*.js | node ./node_modules/coveralls/bin/coveralls.js", | ||
"coverage": "node ./node_modules/mocha/bin/mocha --require blanket --reporter html-cov ./test/*.js > coverage.html", | ||
"complexity": "node ./node_modules/plato/bin/plato -r -d complexity lib", | ||
"performance": "node --allow-natives-syntax --harmony ./node_modules/mocha/bin/_mocha --reporter mocha-performance ./test/*.js", | ||
"lint": "./node_modules/.bin/eslint ./example/*.js ./lib/* ./test/*.js --quiet && echo '✔ All good!'" | ||
"lint": "node ./node_modules/eslint/bin/eslint ./example ./lib ./test --quiet && echo '✔ All good!'" | ||
}, | ||
@@ -52,0 +57,0 @@ "config": { |
@@ -5,2 +5,3 @@ [![Coverage Status](https://coveralls.io/repos/holidayextras/jsonapi-server/badge.svg?branch=master)](https://coveralls.io/r/holidayextras/jsonapi-server?branch=master) | ||
[![Code Climate](https://codeclimate.com/github/holidayextras/jsonapi-server/badges/gpa.svg)](https://codeclimate.com/github/holidayextras/jsonapi-server) | ||
[![Codacy Badge](https://api.codacy.com/project/badge/grade/dd604d6548d8467caa224f078d59b182)](https://www.codacy.com/app/oliver-rumbelow/jsonapi-server) | ||
[![Dependencies Status](https://david-dm.org/holidayextras/jsonapi-server.svg)](https://david-dm.org/holidayextras/jsonapi-server) | ||
@@ -7,0 +8,0 @@ |
@@ -7,3 +7,19 @@ "use strict"; | ||
var swaggerValidator = require("./swaggerValidator.js"); | ||
var profiler = require("v8-profiler"); | ||
var fs = require("fs"); | ||
var path = require("path"); | ||
before(function() { | ||
profiler.startProfiling("", true); | ||
}); | ||
after(function(done) { | ||
var profile = profiler.stopProfiling(""); | ||
var profileFileName = "jsonapi-server.cpuprofile"; | ||
var filePath = path.join(__dirname, "..", profileFileName); | ||
fs.writeFileSync(filePath, JSON.stringify(profile)); | ||
console.error("Saved CPU profile to", filePath); | ||
done(); | ||
}); | ||
testHelpers.validateError = function(json) { | ||
@@ -17,3 +33,3 @@ try { | ||
var keys = Object.keys(json); | ||
assert.deepEqual(keys, [ "meta", "links", "errors" ], "Errors should have specific properties"); | ||
assert.deepEqual(keys, [ "jsonapi", "meta", "links", "errors" ], "Errors should have specific properties"); | ||
assert.equal(typeof json.links.self, "string", "Errors should have a \"self\" link"); | ||
@@ -40,4 +56,6 @@ assert.ok(json.errors instanceof Array, "errors should be an array"); | ||
assert.ok(json instanceof Object, "Response should be an object"); | ||
assert.ok(json.jsonapi instanceof Object, "Response should have a jsonapi block"); | ||
assert.ok(json.meta instanceof Object, "Response should have a meta block"); | ||
assert.ok(json.links instanceof Object, "Response should have a links block"); | ||
assert.ok(!(json.errors instanceof Object), "Response should not have any errors"); | ||
assert.equal(typeof json.links.self, "string", "Response should have a \"self\" link"); | ||
@@ -48,5 +66,2 @@ return json; | ||
testHelpers.validateRelationship = function(relationship) { | ||
var keys = Object.keys(relationship); | ||
assert.deepEqual(keys, [ "meta", "links", "data" ], "Relationships should have specific properties"); | ||
assert.ok(relationship.meta instanceof Object, "Relationships should have a meta block"); | ||
@@ -53,0 +68,0 @@ assert.equal(typeof relationship.meta.relation, "string", "Relationships should have a relation type"); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
203910
67
4935
1
61
12
9
5
+ Addedlodash.assign@3.2.0
+ Addedlodash.omit@3.1.0
+ Addedlodash.pick@3.1.0
+ Addedlodash.uniq@3.2.2
+ Addedlodash._arraymap@3.0.0(transitive)
+ Addedlodash._baseassign@3.2.0(transitive)
+ Addedlodash._basecallback@3.3.1(transitive)
+ Addedlodash._basecopy@3.0.1(transitive)
+ Addedlodash._basedifference@3.0.3(transitive)
+ Addedlodash._baseflatten@3.1.4(transitive)
+ Addedlodash._basefor@3.0.3(transitive)
+ Addedlodash._baseindexof@3.1.0(transitive)
+ Addedlodash._baseisequal@3.0.7(transitive)
+ Addedlodash._baseuniq@3.0.3(transitive)
+ Addedlodash._bindcallback@3.0.1(transitive)
+ Addedlodash._cacheindexof@3.0.2(transitive)
+ Addedlodash._createassigner@3.1.1(transitive)
+ Addedlodash._createcache@3.1.2(transitive)
+ Addedlodash._getnative@3.9.1(transitive)
+ Addedlodash._isiterateecall@3.0.9(transitive)
+ Addedlodash._pickbyarray@3.0.2(transitive)
+ Addedlodash._pickbycallback@3.0.0(transitive)
+ Addedlodash.assign@3.2.0(transitive)
+ Addedlodash.isarguments@3.1.0(transitive)
+ Addedlodash.isarray@3.0.4(transitive)
+ Addedlodash.istypedarray@3.0.6(transitive)
+ Addedlodash.keys@3.1.2(transitive)
+ Addedlodash.keysin@3.0.8(transitive)
+ Addedlodash.omit@3.1.0(transitive)
+ Addedlodash.pairs@3.0.1(transitive)
+ Addedlodash.pick@3.1.0(transitive)
+ Addedlodash.restparam@3.6.1(transitive)
+ Addedlodash.uniq@3.2.2(transitive)
- Removedunderscore@1.8.3
- Removedunderscore@1.8.3(transitive)