Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | ||
4 | 1 | var debug = require("debug"); |
5 | ||
6 | 1 | function overrideDebugOutputHelper(debugFns, outputFnFactory) { |
7 | 0 | Object.keys(debugFns).filter(function(key) { |
8 | 0 | return (key.substr(0, 2) !== "__"); |
9 | }).forEach(function(key) { | |
10 | 0 | if (debugFns[key] instanceof Function) { |
11 | 0 | debugFns[key] = outputFnFactory(debugFns[key].namespace); |
12 | 0 | return null; |
13 | } | |
14 | 0 | return overrideDebugOutputHelper(debugFns[key], outputFnFactory); |
15 | }); | |
16 | } | |
17 | ||
18 | 1 | var debugging = module.exports = { |
19 | handler: { | |
20 | search: debug("jsonApi:handler:search"), | |
21 | find: debug("jsonApi:handler:find"), | |
22 | create: debug("jsonApi:handler:create"), | |
23 | update: debug("jsonApi:handler:update"), | |
24 | delete: debug("jsonApi:handler:delete") | |
25 | }, | |
26 | include: debug("jsonApi:include"), | |
27 | filter: debug("jsonApi:filter"), | |
28 | validationInput: debug("jsonApi:validation:input"), | |
29 | validationOutput: debug("jsonApi:validation:output"), | |
30 | validationError: debug("jsonApi:validation:error"), | |
31 | errors: debug("jsonApi:errors"), | |
32 | requestCounter: debug("jsonApi:requestCounter"), | |
33 | ||
34 | __overrideDebugOutput: function() {} | |
35 | }; | |
36 | ||
37 | 1 | debugging.__overrideDebugOutput = overrideDebugOutputHelper.bind(null, debugging); |
38 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var filter = module.exports = { }; |
4 | ||
5 | ||
6 | 1 | var FILTER_OPERATORS = ["<", ">", "~", ":"]; |
7 | 1 | var STRING_ONLY_OPERATORS = ["~", ":"]; |
8 | ||
9 | ||
10 | 1 | filter._resourceDoesNotHaveProperty = function(resourceConfig, key) { |
11 | 93 | if (resourceConfig.attributes[key]) return null; |
12 | 3 | return { |
13 | status: "403", | |
14 | code: "EFORBIDDEN", | |
15 | title: "Invalid filter", | |
16 | detail: resourceConfig.resource + " do not have attribute or relationship '" + key + "'" | |
17 | }; | |
18 | }; | |
19 | ||
20 | 1 | filter._relationshipIsForeign = function(resourceConfig, key) { |
21 | 45 | var relationSettings = resourceConfig.attributes[key]._settings; |
22 | 89 | if (!relationSettings || !relationSettings.__as) return null; |
23 | 1 | return { |
24 | status: "403", | |
25 | code: "EFORBIDDEN", | |
26 | title: "Invalid filter", | |
27 | detail: "Filter relationship '" + key + "' is a foreign reference and does not exist on " + resourceConfig.resource | |
28 | }; | |
29 | }; | |
30 | ||
31 | 1 | filter._splitElement = function(element) { |
32 | 72 | if (!element) return null; |
33 | 72 | if (FILTER_OPERATORS.indexOf(element[0]) !== -1) { |
34 | 8 | return { operator: element[0], value: element.substring(1) }; |
35 | } | |
36 | 64 | return { operator: null, value: element }; |
37 | }; | |
38 | ||
39 | 1 | filter._stringOnlyOperator = function(operator, attributeConfig) { |
40 | 136 | if (!operator || !attributeConfig) return null; |
41 | 8 | if (STRING_ONLY_OPERATORS.indexOf(operator) !== -1 && attributeConfig._type !== "string") { |
42 | 0 | return "operator " + operator + " can only be applied to string attributes"; |
43 | } | |
44 | 8 | return null; |
45 | }; | |
46 | ||
47 | 1 | filter._parseScalarFilterElement = function(attributeConfig, scalarElement) { |
48 | 72 | if (!scalarElement) return { error: "invalid or empty filter element" }; |
49 | ||
50 | 72 | var splitElement = filter._splitElement(scalarElement); |
51 | 72 | if (!splitElement) return { error: "empty filter" }; |
52 | ||
53 | 72 | var error = filter._stringOnlyOperator(splitElement.operator, attributeConfig); |
54 | 72 | if (error) return { error: error }; |
55 | ||
56 | 72 | if (attributeConfig._settings) { // relationship attribute: no further validation |
57 | 17 | return { result: splitElement }; |
58 | } | |
59 | ||
60 | 55 | var validateResult = attributeConfig.validate(splitElement.value); |
61 | 55 | if (validateResult.error) { |
62 | 1 | return { error: validateResult.error.message }; |
63 | } | |
64 | ||
65 | 54 | var validatedElement = { operator: splitElement.operator, value: validateResult.value }; |
66 | 54 | return { result: validatedElement }; |
67 | }; | |
68 | ||
69 | 1 | filter._parseFilterElementHelper = function(attributeConfig, filterElement) { |
70 | 44 | if (!filterElement) return { error: "invalid or empty filter element" }; |
71 | ||
72 | 44 | var parsedElements = [].concat(filterElement).map(function(scalarElement) { |
73 | 72 | return filter._parseScalarFilterElement(attributeConfig, scalarElement); |
74 | }); | |
75 | ||
76 | 76 | if (parsedElements.length === 1) return parsedElements[0]; |
77 | ||
78 | 12 | var errors = parsedElements.reduce(function(combined, element) { |
79 | 40 | if (!combined) { |
80 | 80 | if (!element.error) return combined; |
81 | 0 | return [ element.error ]; |
82 | } | |
83 | 0 | return combined.concat(element.error); |
84 | }, null); | |
85 | ||
86 | 12 | if (errors) return { error: errors }; |
87 | ||
88 | 12 | var results = parsedElements.map(function(element) { |
89 | 40 | return element.result; |
90 | }); | |
91 | ||
92 | 12 | return { result: results }; |
93 | }; | |
94 | ||
95 | 1 | filter._parseFilterElement = function(attributeName, attributeConfig, filterElement) { |
96 | 44 | var helperResult = filter._parseFilterElementHelper(attributeConfig, filterElement); |
97 | ||
98 | 44 | if (helperResult.error) { |
99 | 1 | return { |
100 | error: { | |
101 | status: "403", | |
102 | code: "EFORBIDDEN", | |
103 | title: "Invalid filter", | |
104 | detail: "Filter value for key '" + attributeName + "' is invalid: " + helperResult.error | |
105 | } | |
106 | }; | |
107 | } | |
108 | 43 | return { result: helperResult.result }; |
109 | }; | |
110 | ||
111 | 1 | filter.parseAndValidate = function(request) { |
112 | 120 | if (!request.params.filter) return null; |
113 | ||
114 | 46 | var resourceConfig = request.resourceConfig; |
115 | ||
116 | 46 | var processedFilter = { }; |
117 | 46 | var error; |
118 | 46 | var filterElement; |
119 | 46 | var parsedFilterElement; |
120 | ||
121 | 46 | for (var key in request.params.filter) { |
122 | 49 | filterElement = request.params.filter[key]; |
123 | ||
124 | 50 | if (!Array.isArray(filterElement) && filterElement instanceof Object) continue; // skip deep filters |
125 | ||
126 | 48 | error = filter._resourceDoesNotHaveProperty(resourceConfig, key); |
127 | 51 | if (error) return error; |
128 | ||
129 | 45 | error = filter._relationshipIsForeign(resourceConfig, key); |
130 | 46 | if (error) return error; |
131 | ||
132 | 44 | parsedFilterElement = filter._parseFilterElement(key, resourceConfig.attributes[key], filterElement); |
133 | 45 | if (parsedFilterElement.error) return parsedFilterElement.error; |
134 | ||
135 | 43 | processedFilter[key] = [].concat(parsedFilterElement.result); |
136 | } | |
137 | ||
138 | 41 | request.processedFilter = processedFilter; |
139 | ||
140 | 41 | return null; |
141 | }; | |
142 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var handlerEnforcer = module.exports = { }; |
4 | ||
5 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js"); |
6 | ||
7 | 1 | handlerEnforcer.wrap = function(handlers) { |
8 | 5 | handlers.search = handlerEnforcer._search(handlers); |
9 | 5 | handlers.find = handlerEnforcer._find(handlers); |
10 | 5 | handlers.create = handlerEnforcer._create(handlers); |
11 | 5 | handlers.update = handlerEnforcer._update(handlers); |
12 | 5 | handlers.delete = handlerEnforcer._delete(handlers); |
13 | }; | |
14 | ||
15 | 1 | handlerEnforcer._wrapHandler = function(handlers, operation, outCount) { |
16 | 25 | if (typeof outCount !== "number") { |
17 | 0 | throw new Error("Invalid use of handlerEnforcer._wrapHandler!"); |
18 | } | |
19 | ||
20 | 25 | var original = handlers[operation]; |
21 | 26 | if (!original) return null; |
22 | 24 | return function() { |
23 | 125 | var argsIn = Array.prototype.slice.call(arguments); |
24 | 125 | var requestParams = argsIn[0].params; |
25 | 125 | var callback = argsIn.pop(); |
26 | 125 | argsIn.push(function() { |
27 | 125 | var argsOut = Array.prototype.slice.call(arguments); |
28 | 125 | argsOut = argsOut.slice(0, outCount); |
29 | // $FlowFixMe: We've already ruled out any other possible types for outCount? | |
30 | 125 | while (argsOut.length < outCount) { |
31 | 12 | argsOut.push(null); |
32 | } | |
33 | 125 | debug.handler[operation](JSON.stringify(requestParams), JSON.stringify(argsOut)); |
34 | 125 | return callback.apply(null, argsOut); |
35 | }); | |
36 | 125 | original.apply(handlers, argsIn); |
37 | }; | |
38 | }; | |
39 | ||
40 | 1 | handlerEnforcer._search = function(handlers) { |
41 | 5 | return handlerEnforcer._wrapHandler(handlers, "search", 3); |
42 | }; | |
43 | ||
44 | 1 | handlerEnforcer._find = function(handlers) { |
45 | 5 | return handlerEnforcer._wrapHandler(handlers, "find", 2); |
46 | }; | |
47 | ||
48 | 1 | handlerEnforcer._create = function(handlers) { |
49 | 5 | return handlerEnforcer._wrapHandler(handlers, "create", 2); |
50 | }; | |
51 | ||
52 | 1 | handlerEnforcer._update = function(handlers) { |
53 | 5 | return handlerEnforcer._wrapHandler(handlers, "update", 2); |
54 | }; | |
55 | ||
56 | 1 | handlerEnforcer._delete = function(handlers) { |
57 | 5 | return handlerEnforcer._wrapHandler(handlers, "delete", 1); |
58 | }; | |
59 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var jsonApi = module.exports = { }; |
4 | 1 | jsonApi._version = require(require("path").join(__dirname, "../package.json")).version; |
5 | 1 | jsonApi._resources = { }; |
6 | 1 | jsonApi._apiConfig = { }; |
7 | ||
8 | 1 | var _ = { |
9 | assign: require("lodash.assign"), | |
10 | pick: require("lodash.pick") | |
11 | }; | |
12 | 1 | var ourJoi = require("/home/pmcnr/work/jsonapi-server/lib/./ourJoi.js"); |
13 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/./router.js"); |
14 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/./responseHelper.js"); |
15 | 1 | var handlerEnforcer = require("/home/pmcnr/work/jsonapi-server/lib/./handlerEnforcer.js"); |
16 | 1 | var pagination = require("/home/pmcnr/work/jsonapi-server/lib/./pagination.js"); |
17 | 1 | var routes = require("/home/pmcnr/work/jsonapi-server/lib/./routes"); |
18 | 1 | var url = require("url"); |
19 | ||
20 | 1 | jsonApi.Joi = ourJoi.Joi; |
21 | 1 | jsonApi.MemoryHandler = require("/home/pmcnr/work/jsonapi-server/lib/./MemoryHandler"); |
22 | ||
23 | 1 | jsonApi.setConfig = function(apiConfig) { |
24 | 1 | jsonApi._apiConfig = apiConfig; |
25 | 1 | jsonApi._apiConfig.base = jsonApi._cleanBaseUrl(jsonApi._apiConfig.base); |
26 | 1 | jsonApi._apiConfig.pathPrefix = jsonApi._concatenateUrlPrefix(jsonApi._apiConfig); |
27 | 1 | responseHelper.setBaseUrl(jsonApi._apiConfig.pathPrefix); |
28 | 1 | responseHelper.setMetadata(jsonApi._apiConfig.meta); |
29 | }; | |
30 | ||
31 | 1 | jsonApi.authenticate = router.authenticateWith; |
32 | ||
33 | 1 | jsonApi._concatenateUrlPrefix = function(config) { |
34 | 1 | return url.format({ |
35 | protocol: config.protocol, | |
36 | hostname: config.hostname, | |
37 | port: config.port, | |
38 | pathname: config.base | |
39 | }); | |
40 | }; | |
41 | ||
42 | 1 | jsonApi._cleanBaseUrl = function(base) { |
43 | 1 | if (!base) { |
44 | 0 | base = ""; |
45 | } | |
46 | 1 | if (base[0] !== "/") { |
47 | 1 | base = "/" + base; |
48 | } | |
49 | 1 | if (base[base.length - 1] !== "/") { |
50 | 1 | base += "/"; |
51 | } | |
52 | 1 | return base; |
53 | }; | |
54 | ||
55 | 1 | jsonApi.define = function(resourceConfig) { |
56 | 5 | resourceConfig.namespace = resourceConfig.namespace || "default"; |
57 | 5 | resourceConfig.searchParams = resourceConfig.searchParams || { }; |
58 | 5 | jsonApi._resources[resourceConfig.resource] = resourceConfig; |
59 | ||
60 | 5 | handlerEnforcer.wrap(resourceConfig.handlers); |
61 | ||
62 | 5 | if (resourceConfig.handlers.initialise) { |
63 | 5 | resourceConfig.handlers.initialise(resourceConfig); |
64 | } | |
65 | ||
66 | 5 | Object.keys(resourceConfig.attributes).forEach(function(attribute) { |
67 | 29 | if (!attribute.match(/^[A-Za-z0-9\-\_]*$/)) { |
68 | 0 | throw new Error("Attribute '" + attribute + "' on " + resourceConfig.resource + " contains illegal characters!"); |
69 | } | |
70 | }); | |
71 | ||
72 | 5 | resourceConfig.searchParams = _.assign({ |
73 | type: ourJoi.Joi.any().required().valid(resourceConfig.resource) | |
74 | .description("Always \"" + resourceConfig.resource + "\"") | |
75 | .example(resourceConfig.resource), | |
76 | sort: ourJoi.Joi.any() | |
77 | .description("An attribute to sort by") | |
78 | .example("title"), | |
79 | filter: ourJoi.Joi.any() | |
80 | .description("An attribute+value to filter by") | |
81 | .example("title"), | |
82 | fields: ourJoi.Joi.any() | |
83 | .description("An attribute+value to filter by") | |
84 | .example("title"), | |
85 | include: ourJoi.Joi.any() | |
86 | .description("An attribute to include") | |
87 | .example("title") | |
88 | }, resourceConfig.searchParams, pagination.joiPageDefinition); | |
89 | ||
90 | 5 | resourceConfig.attributes = _.assign({ |
91 | id: ourJoi.Joi.string().required() | |
92 | .description("Unique resource identifier") | |
93 | .example("1234"), | |
94 | type: ourJoi.Joi.string().required().valid(resourceConfig.resource) | |
95 | .description("Always \"" + resourceConfig.resource + "\"") | |
96 | .example(resourceConfig.resource), | |
97 | meta: ourJoi.Joi.object().optional() | |
98 | }, resourceConfig.attributes); | |
99 | ||
100 | 5 | resourceConfig.onCreate = _.pick.apply(_, [].concat(resourceConfig.attributes, Object.keys(resourceConfig.attributes).filter(function(i) { |
101 | 44 | return (resourceConfig.attributes[i]._meta.indexOf("readonly") === -1) && (!(resourceConfig.attributes[i]._settings || { }).__as); |
102 | }))); | |
103 | }; | |
104 | ||
105 | 1 | jsonApi.onUncaughtException = function(errHandler) { |
106 | 1 | jsonApi._errHandler = errHandler; |
107 | }; | |
108 | ||
109 | 1 | jsonApi.start = function() { |
110 | 20 | routes.register(); |
111 | 20 | router.listen(jsonApi._apiConfig.port); |
112 | }; | |
113 | ||
114 | 1 | jsonApi.close = function() { |
115 | 19 | router.close(); |
116 | 19 | for (var i in jsonApi._resources) { |
117 | 95 | var resourceConfig = jsonApi._resources[i]; |
118 | 95 | if (resourceConfig.handlers.close) resourceConfig.handlers.close(); |
119 | } | |
120 | }; | |
121 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var _ = { |
4 | assign: require("lodash.assign") | |
5 | }; | |
6 | ||
7 | 1 | var MemoryStore = module.exports = function MemoryStore() { |
8 | }; | |
9 | ||
10 | // resources represents out in-memory data store | |
11 | 1 | var resources = { }; |
12 | ||
13 | /** | |
14 | Handlers readiness status. This should be set to `true` once all handlers are ready to process requests. | |
15 | */ | |
16 | 1 | MemoryStore.prototype.ready = false; |
17 | ||
18 | /** | |
19 | initialise gets invoked once for each resource that uses this hander. | |
20 | In this instance, we're allocating an array in our in-memory data store. | |
21 | */ | |
22 | 1 | MemoryStore.prototype.initialise = function(resourceConfig) { |
23 | 5 | resources[resourceConfig.resource] = resourceConfig.examples || [ ]; |
24 | 5 | this.ready = true; |
25 | }; | |
26 | ||
27 | /** | |
28 | Search for a list of resources, given a resource type. | |
29 | */ | |
30 | 1 | MemoryStore.prototype.search = function(request, callback) { |
31 | 67 | var self = this; |
32 | ||
33 | 67 | var results = [].concat(resources[request.params.type]); |
34 | 67 | self._sortList(request, results); |
35 | 67 | var resultCount = results.length; |
36 | 67 | if (request.params.page) { |
37 | 66 | results = results.slice(request.params.page.offset, request.params.page.offset + request.params.page.limit); |
38 | } | |
39 | 67 | return callback(null, results, resultCount); |
40 | }; | |
41 | ||
42 | /** | |
43 | Find a specific resource, given a resource type and and id. | |
44 | */ | |
45 | 1 | MemoryStore.prototype.find = function(request, callback) { |
46 | // Pull the requested resource from the in-memory store | |
47 | 47 | var theResource = resources[request.params.type].filter(function(anyResource) { |
48 | 162 | return anyResource.id === request.params.id; |
49 | }).pop(); | |
50 | ||
51 | // If the resource doesn't exist, error | |
52 | 47 | if (!theResource) { |
53 | 9 | return callback({ |
54 | status: "404", | |
55 | code: "ENOTFOUND", | |
56 | title: "Requested resource does not exist", | |
57 | detail: "There is no " + request.params.type + " with id " + request.params.id | |
58 | }); | |
59 | } | |
60 | ||
61 | // Return the requested resource | |
62 | 38 | return callback(null, theResource); |
63 | }; | |
64 | ||
65 | /** | |
66 | Create (store) a new resource give a resource type and an object. | |
67 | */ | |
68 | 1 | MemoryStore.prototype.create = function(request, newResource, callback) { |
69 | // Push the newResource into our in-memory store. | |
70 | 1 | resources[request.params.type].push(newResource); |
71 | // Return the newly created resource | |
72 | 1 | return callback(null, newResource); |
73 | }; | |
74 | ||
75 | /** | |
76 | Delete a resource, given a resource type and and id. | |
77 | */ | |
78 | 1 | MemoryStore.prototype.delete = function(request, callback) { |
79 | // Find the requested resource | |
80 | 2 | this.find(request, function(err, theResource) { |
81 | 3 | if (err) return callback(err); |
82 | ||
83 | // Remove the resource from the in-meory store. | |
84 | 1 | var resourceIndex = resources[request.params.type].indexOf(theResource); |
85 | 1 | resources[request.params.type].splice(resourceIndex, 1); |
86 | ||
87 | // Return with no error | |
88 | 1 | return callback(); |
89 | }); | |
90 | }; | |
91 | ||
92 | /** | |
93 | Update a resource, given a resource type and id, along with a partialResource. | |
94 | partialResource contains a subset of changes that need to be merged over the original. | |
95 | */ | |
96 | 1 | MemoryStore.prototype.update = function(request, partialResource, callback) { |
97 | // Find the requested resource | |
98 | 8 | this.find(request, function(err, theResource) { |
99 | 10 | if (err) return callback(err); |
100 | ||
101 | // Merge the partialResource over the original | |
102 | 6 | theResource = _.assign(theResource, partialResource); |
103 | ||
104 | // Push the newly updated resource back into the in-memory store | |
105 | 6 | resources[request.params.type][request.params.id] = theResource; |
106 | ||
107 | // Return the newly updated resource | |
108 | 6 | return callback(null, theResource); |
109 | }); | |
110 | }; | |
111 | ||
112 | /** | |
113 | Internal helper function to sort data | |
114 | */ | |
115 | 1 | MemoryStore.prototype._sortList = function(request, list) { |
116 | 67 | var attribute = request.params.sort; |
117 | 127 | if (!attribute) return; |
118 | ||
119 | 7 | var ascending = 1; |
120 | 7 | attribute = ("" + attribute); |
121 | 7 | if (attribute[0] === "-") { |
122 | 1 | ascending = -1; |
123 | 1 | attribute = attribute.substring(1, attribute.length); |
124 | } | |
125 | ||
126 | 7 | list.sort(function(a, b) { |
127 | 29 | if (typeof a[attribute] === "string") { |
128 | 29 | return a[attribute].localeCompare(b[attribute]) * ascending; |
129 | 0 | } else if (typeof a[attribute] === "number") { |
130 | 0 | return (a[attribute] - b[attribute]) * ascending; |
131 | } else { | |
132 | 0 | return 0; |
133 | } | |
134 | }); | |
135 | }; | |
136 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var ourJoi = module.exports = { }; |
4 | ||
5 | ||
6 | 1 | var Joi = require("joi"); |
7 | ||
8 | 1 | ourJoi._joiBase = function(resourceName) { |
9 | 13 | var relationType = Joi.object().keys({ |
10 | id: Joi.string().required(), | |
11 | type: Joi.any().required().valid(resourceName), | |
12 | meta: Joi.object().optional() | |
13 | }); | |
14 | 13 | return relationType; |
15 | }; | |
16 | 1 | Joi.one = function(resource) { |
17 | 4 | var obj = Joi.alternatives().try( |
18 | Joi.any().valid(null), // null | |
19 | ourJoi._joiBase(resource) | |
20 | ); | |
21 | 4 | obj._settings = { |
22 | __one: resource | |
23 | }; | |
24 | 4 | return obj; |
25 | }; | |
26 | 1 | Joi.many = function(resource) { |
27 | 3 | var obj = Joi.array().items(ourJoi._joiBase(resource)); |
28 | 3 | obj._settings = { |
29 | __many: resource | |
30 | }; | |
31 | 3 | return obj; |
32 | }; | |
33 | 1 | Joi._validateForeignRelation = function(config) { |
34 | 6 | if (!config.as) throw new Error("Missing 'as' property when defining a foreign relation"); |
35 | 6 | if (!config.resource) throw new Error("Missing 'resource' property when defining a foreign relation"); |
36 | }; | |
37 | 1 | Joi.belongsToOne = function(config) { |
38 | 1 | Joi._validateForeignRelation(config); |
39 | 1 | var obj = Joi.alternatives().try( |
40 | Joi.any().valid(null), // null | |
41 | ourJoi._joiBase(config.resource) | |
42 | ); | |
43 | 1 | obj._settings = { |
44 | __one: config.resource, | |
45 | __as: config.as | |
46 | }; | |
47 | 1 | return obj; |
48 | }; | |
49 | 1 | Joi.belongsToMany = function(config) { |
50 | 5 | Joi._validateForeignRelation(config); |
51 | 5 | var obj = Joi.array().items(ourJoi._joiBase(config.resource)); |
52 | 5 | obj._settings = { |
53 | __many: config.resource, | |
54 | __as: config.as | |
55 | }; | |
56 | 5 | return obj; |
57 | }; | |
58 | 1 | ourJoi.Joi = Joi; |
59 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var pagination = module.exports = { }; |
4 | ||
5 | 1 | var ourJoi = require("/home/pmcnr/work/jsonapi-server/lib/./ourJoi.js"); |
6 | 1 | var url = require("url"); |
7 | ||
8 | ||
9 | 1 | pagination.joiPageDefinition = { |
10 | page: ourJoi.Joi.object().keys({ | |
11 | offset: ourJoi.Joi.number() | |
12 | .description("The first record to appear in the resulting payload") | |
13 | .example(0), | |
14 | limit: ourJoi.Joi.number() | |
15 | .description("The number of records to appear in the resulting payload") | |
16 | .example(50) | |
17 | }) | |
18 | }; | |
19 | ||
20 | 1 | pagination.generateMetaSummary = function(request, handlerTotal) { |
21 | 66 | return { |
22 | offset: request.params.page.offset, | |
23 | limit: request.params.page.limit, | |
24 | total: handlerTotal | |
25 | }; | |
26 | }; | |
27 | ||
28 | 1 | pagination.validatePaginationParams = function(request) { |
29 | 66 | if (!request.params.page) { |
30 | 61 | request.params.page = { }; |
31 | } | |
32 | 66 | var page = request.params.page; |
33 | ||
34 | 66 | page.offset = parseInt(page.offset, 10) || 0; |
35 | 66 | page.limit = parseInt(page.limit, 10) || 50; |
36 | }; | |
37 | ||
38 | 1 | pagination.enforcePagination = function(request, results) { |
39 | 66 | return results.slice(0, request.params.page.size); |
40 | }; | |
41 | ||
42 | 1 | pagination.generatePageLinks = function(request, handlerTotal) { |
43 | 93 | var pageData = request.params.page; |
44 | 93 | if (!handlerTotal || !pageData) { |
45 | 27 | return { }; |
46 | } | |
47 | ||
48 | 66 | var lowerLimit = pageData.offset; |
49 | 66 | var upperLimit = pageData.offset + pageData.limit; |
50 | ||
51 | 66 | if ((lowerLimit === 0) && (upperLimit > handlerTotal)) { |
52 | 61 | return { }; |
53 | } | |
54 | ||
55 | 5 | var pageLinks = { }; |
56 | 5 | var theirRequest = url.parse(request.route.combined, true); |
57 | 5 | theirRequest.search = null; |
58 | ||
59 | 5 | if (lowerLimit > 0) { |
60 | 4 | theirRequest.query["page[offset]"] = 0; |
61 | 4 | pageLinks.first = url.format(theirRequest); |
62 | ||
63 | 4 | if (pageData.offset > 0) { |
64 | 4 | var previousPageOffset = pageData.offset - pageData.limit; |
65 | 4 | if (previousPageOffset < 0) { |
66 | 1 | previousPageOffset = 0; |
67 | } | |
68 | 4 | theirRequest.query["page[offset]"] = previousPageOffset; |
69 | 4 | pageLinks.prev = url.format(theirRequest); |
70 | } | |
71 | } | |
72 | ||
73 | 5 | if (upperLimit < handlerTotal) { |
74 | 4 | var lastPage = (Math.floor(handlerTotal / pageData.limit) * pageData.limit) - 1; |
75 | 4 | theirRequest.query["page[offset]"] = lastPage; |
76 | 4 | pageLinks.last = url.format(theirRequest); |
77 | ||
78 | 4 | if ((pageData.offset + pageData.limit) < handlerTotal) { |
79 | 4 | var nextPageOffset = pageData.offset + pageData.limit; |
80 | 4 | theirRequest.query["page[offset]"] = nextPageOffset; |
81 | 4 | pageLinks.next = url.format(theirRequest); |
82 | } | |
83 | } | |
84 | ||
85 | 5 | return pageLinks; |
86 | }; | |
87 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var postProcess = module.exports = { }; |
4 | ||
5 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/.."); |
6 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js"); |
7 | 1 | var rerouter = require("/home/pmcnr/work/jsonapi-server/lib/./rerouter.js"); |
8 | 1 | var async = require("async"); |
9 | 1 | postProcess._applySort = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/sort.js").action; |
10 | 1 | postProcess._applyFilter = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/filter.js").action; |
11 | 1 | postProcess._applyIncludes = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/include.js").action; |
12 | 1 | postProcess._applyFields = require("/home/pmcnr/work/jsonapi-server/lib/./postProcessing/fields.js").action; |
13 | ||
14 | 1 | postProcess.handle = function(request, response, callback) { |
15 | 89 | async.waterfall([ |
16 | function(next) { | |
17 | 89 | return postProcess._applySort(request, response, next); |
18 | }, | |
19 | function(next) { | |
20 | 89 | return postProcess._applyFilter(request, response, next); |
21 | }, | |
22 | function(next) { | |
23 | 89 | return postProcess._applyIncludes(request, response, next); |
24 | }, | |
25 | function(next) { | |
26 | 89 | return postProcess._applyFields(request, response, next); |
27 | } | |
28 | ], function(err) { | |
29 | 89 | return callback(err); |
30 | }); | |
31 | }; | |
32 | ||
33 | 1 | postProcess._fetchRelatedResources = function(request, mainResource, callback) { |
34 | ||
35 | // Fetch the other objects | |
36 | 5 | var dataItems = mainResource[request.params.relation]; |
37 | ||
38 | 6 | if (!dataItems) return callback(null, [ null ]); |
39 | ||
40 | 8 | if (!(dataItems instanceof Array)) dataItems = [ dataItems ]; |
41 | ||
42 | 4 | var resourcesToFetch = dataItems.reduce(function(map, dataItem) { |
43 | 4 | map[dataItem.type] = map[dataItem.type] || [ ]; |
44 | 4 | map[dataItem.type].push(dataItem.id); |
45 | 4 | return map; |
46 | }, { }); | |
47 | ||
48 | 4 | resourcesToFetch = Object.keys(resourcesToFetch).map(function(type) { |
49 | 4 | var ids = resourcesToFetch[type]; |
50 | 4 | var urlJoiner = "&filter[id]="; |
51 | 4 | ids = urlJoiner + ids.join(urlJoiner); |
52 | 4 | var uri = jsonApi._apiConfig.pathPrefix + type + "/?" + ids; |
53 | 4 | if (request.route.query) { |
54 | 3 | uri += "&" + request.route.query; |
55 | } | |
56 | 4 | return uri; |
57 | }); | |
58 | ||
59 | 4 | async.map(resourcesToFetch, function(related, done) { |
60 | 4 | debug.include(related); |
61 | ||
62 | 4 | rerouter.route({ |
63 | method: "GET", | |
64 | uri: related, | |
65 | originalRequest: request | |
66 | }, function(err, json) { | |
67 | 4 | if (err) { |
68 | 0 | debug.include("!!", JSON.stringify(err)); |
69 | 0 | return done(err.errors); |
70 | } | |
71 | ||
72 | 4 | var data = json.data; |
73 | 4 | if (!(data instanceof Array)) data = [ data ]; |
74 | 4 | return done(null, data); |
75 | }); | |
76 | }, function(err, otherResources) { | |
77 | 4 | if (err) return callback(err); |
78 | 4 | var relatedResources = [].concat.apply([], otherResources); |
79 | 4 | return callback(null, relatedResources); |
80 | }); | |
81 | }; | |
82 | ||
83 | 1 | postProcess.fetchForeignKeys = function(request, items, schema, callback) { |
84 | 87 | if (!(items instanceof Array)) { |
85 | 21 | items = [ items ]; |
86 | } | |
87 | 87 | items.forEach(function(item) { |
88 | 266 | for (var i in schema) { |
89 | 2709 | var settings = schema[i]._settings; |
90 | 2709 | if (settings && settings.__as) { |
91 | 209 | item[i] = undefined; |
92 | } | |
93 | } | |
94 | }); | |
95 | 87 | return callback(); |
96 | }; | |
97 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var fields = module.exports = { }; |
4 | ||
5 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../jsonApi.js"); |
6 | ||
7 | 1 | fields.action = function(request, response, callback) { |
8 | 89 | var resourceList = request.params.fields; |
9 | 172 | if (!resourceList || !(resourceList instanceof Object)) return callback(); |
10 | ||
11 | 6 | var allDataItems = response.included.concat(response.data); |
12 | ||
13 | 6 | for (var resource in resourceList) { |
14 | 6 | if (!jsonApi._resources[resource]) { |
15 | 1 | return callback({ |
16 | status: "403", | |
17 | code: "EFORBIDDEN", | |
18 | title: "Invalid field resource", | |
19 | detail: resource + " is not a valid resource " | |
20 | }); | |
21 | } | |
22 | ||
23 | 5 | var field = ("" + resourceList[resource]).split(","); |
24 | ||
25 | 5 | for (var i = 0; i < field.length; i++) { |
26 | 6 | var j = field[i]; |
27 | 6 | if (!jsonApi._resources[resource].attributes[j]) { |
28 | 0 | return callback({ |
29 | status: "403", | |
30 | code: "EFORBIDDEN", | |
31 | title: "Invalid field selection", | |
32 | detail: resource + " do not have property " + j | |
33 | }); | |
34 | } | |
35 | } | |
36 | } | |
37 | ||
38 | 5 | allDataItems.forEach(function(dataItem) { |
39 | 11 | Object.keys(dataItem.attributes).forEach(function(attribute) { |
40 | 49 | if (field.indexOf(attribute) === -1) { |
41 | 34 | delete dataItem.attributes[attribute]; |
42 | } | |
43 | }); | |
44 | }); | |
45 | ||
46 | 5 | return callback(); |
47 | }; | |
48 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var filter = module.exports = { }; |
4 | ||
5 | 1 | var _ = { |
6 | assign: require("lodash.assign"), | |
7 | isEqual: require("lodash.isequal") | |
8 | }; | |
9 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../debugging.js"); |
10 | ||
11 | 1 | filter.action = function(request, response, callback) { |
12 | 89 | var filters = request.processedFilter; |
13 | 137 | if (!filters) return callback(); |
14 | ||
15 | 41 | if (response.data instanceof Array) { |
16 | 40 | for (var j = 0; j < response.data.length; j++) { |
17 | 157 | if (!filter._filterKeepObject(response.data[j], filters)) { |
18 | 88 | debug.filter("removed", filters, JSON.stringify(response.data[j].attributes)); |
19 | 88 | response.data.splice(j, 1); |
20 | 88 | j--; |
21 | } | |
22 | } | |
23 | 1 | } else if (response.data instanceof Object) { |
24 | 1 | if (!filter._filterKeepObject(response.data, filters)) { |
25 | 1 | debug.filter("removed", filters, JSON.stringify(response.data.attributes)); |
26 | 1 | response.data = null; |
27 | } | |
28 | } | |
29 | ||
30 | 41 | return callback(); |
31 | }; | |
32 | ||
33 | 1 | filter._filterMatches = function(filterElement, attributeValue) { |
34 | 203 | if (!filterElement.operator) { |
35 | 171 | return _.isEqual(attributeValue, filterElement.value); |
36 | } | |
37 | 32 | var filterFunction = { |
38 | ">": function filterGreaterThan(attrValue, filterValue) { | |
39 | 8 | return attrValue > filterValue; |
40 | }, | |
41 | "<": function filterLessThan(attrValue, filterValue) { | |
42 | 8 | return attrValue < filterValue; |
43 | }, | |
44 | "~": function filterCaseInsensitiveEqual(attrValue, filterValue) { | |
45 | 8 | return attrValue.toLowerCase() === filterValue.toLowerCase(); |
46 | }, | |
47 | ":": function filterCaseInsensitiveContains(attrValue, filterValue) { | |
48 | 8 | return attrValue.toLowerCase().indexOf(filterValue.toLowerCase()) !== -1; |
49 | } | |
50 | }[filterElement.operator]; | |
51 | 32 | var result = filterFunction(attributeValue, filterElement.value); |
52 | 32 | return result; |
53 | }; | |
54 | ||
55 | 1 | filter._filterKeepObject = function(someObject, filters) { |
56 | 158 | for (var filterName in filters) { |
57 | 160 | var whitelist = filters[filterName]; |
58 | ||
59 | 160 | if (someObject.attributes.hasOwnProperty(filterName) || (filterName === "id")) { |
60 | 127 | var attributeValue = someObject.attributes[filterName]; |
61 | 201 | if (filterName === "id") attributeValue = someObject.id; |
62 | 127 | var attributeMatches = filter._attributesMatchesOR(attributeValue, whitelist); |
63 | 199 | if (!attributeMatches) return false; |
64 | 33 | } else if (someObject.relationships.hasOwnProperty(filterName)) { |
65 | 33 | var relationships = someObject.relationships[filterName]; |
66 | 33 | var relationshipMatches = filter._relationshipMatchesOR(relationships, whitelist); |
67 | 50 | if (!relationshipMatches) return false; |
68 | } else { | |
69 | 0 | return false; |
70 | } | |
71 | } | |
72 | 69 | return true; |
73 | }; | |
74 | ||
75 | 1 | filter._attributesMatchesOR = function(attributeValue, whitelist) { |
76 | 127 | var matchOR = false; |
77 | 127 | whitelist.forEach(function(filterElement) { |
78 | 203 | if (filter._filterMatches(filterElement, attributeValue)) { |
79 | 55 | matchOR = true; |
80 | } | |
81 | }); | |
82 | 127 | return matchOR; |
83 | }; | |
84 | ||
85 | 1 | filter._relationshipMatchesOR = function(relationships, whitelist) { |
86 | 33 | var matchOR = false; |
87 | ||
88 | 33 | var data = relationships.data; |
89 | 33 | if (!data) return false; |
90 | ||
91 | 54 | if (!(data instanceof Array)) data = [ data ]; |
92 | 33 | data = data.map(function(relation) { |
93 | 33 | return relation.id; |
94 | }); | |
95 | ||
96 | 33 | whitelist.forEach(function(filterElement) { |
97 | 59 | if (data.indexOf(filterElement.value) !== -1) { |
98 | 16 | matchOR = true; |
99 | } | |
100 | }); | |
101 | 33 | return matchOR; |
102 | }; | |
103 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var includePP = module.exports = { }; |
4 | ||
5 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../jsonApi.js"); |
6 | 1 | var _ = { |
7 | uniq: require("lodash.uniq"), | |
8 | uniqBy: require("lodash.uniqby") | |
9 | }; | |
10 | 1 | var rerouter = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../rerouter.js"); |
11 | 1 | var async = require("async"); |
12 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/postProcessing/../debugging.js"); |
13 | ||
14 | 1 | includePP.action = function(request, response, callback) { |
15 | 89 | var includes = request.params.include; |
16 | 89 | var filters = request.params.filter || { }; |
17 | 166 | if (!includes) return callback(); |
18 | 12 | includes = ("" + includes).split(","); |
19 | ||
20 | 12 | includePP._arrayToTree(request, includes, filters, function(attErr, includeTree) { |
21 | 14 | if (attErr) return callback(attErr); |
22 | ||
23 | 12 | var dataItems = response.data; |
24 | 16 | if (!(dataItems instanceof Array)) dataItems = [ dataItems ]; |
25 | 12 | includeTree._dataItems = dataItems; |
26 | ||
27 | 12 | includePP._fillIncludeTree(includeTree, request, function(fiErr) { |
28 | 12 | if (fiErr) return callback(fiErr); |
29 | ||
30 | 12 | includeTree._dataItems = [ ]; |
31 | 12 | response.included = includePP._getDataItemsFromTree(includeTree); |
32 | 12 | response.included = _.uniqBy(response.included, function(someItem) { |
33 | 39 | return someItem.type + "~~" + someItem.id; |
34 | }); | |
35 | ||
36 | 12 | return callback(); |
37 | }); | |
38 | }); | |
39 | }; | |
40 | ||
41 | 1 | includePP._arrayToTree = function(request, includes, filters, callback) { |
42 | 12 | var tree = { |
43 | _dataItems: null, | |
44 | _resourceConfig: request.resourceConfig | |
45 | }; | |
46 | ||
47 | 12 | var iterate = function(text, node, filter) { |
48 | 47 | if (text.length === 0) return null; |
49 | 21 | var parts = text.split("."); |
50 | 21 | var first = parts.shift(); |
51 | 21 | var rest = parts.join("."); |
52 | ||
53 | 21 | var resourceAttribute = node._resourceConfig.attributes[first]; |
54 | 21 | if (!resourceAttribute) { |
55 | 1 | return callback({ |
56 | status: "403", | |
57 | code: "EFORBIDDEN", | |
58 | title: "Invalid inclusion", | |
59 | detail: node._resourceConfig.resource + " do not have property " + first | |
60 | }); | |
61 | } | |
62 | 20 | resourceAttribute = resourceAttribute._settings.__one || resourceAttribute._settings.__many; |
63 | 20 | if (!resourceAttribute) { |
64 | 0 | return callback({ |
65 | status: "403", | |
66 | code: "EFORBIDDEN", | |
67 | title: "Invalid inclusion", | |
68 | detail: node._resourceConfig.resource + "." + first + " is not a relation and cannot be included" | |
69 | }); | |
70 | } | |
71 | ||
72 | 20 | filter = filter[first] || { }; |
73 | 20 | if (filter instanceof Array) { |
74 | 1 | filter = filter.filter(function(i) { |
75 | 2 | return i instanceof Object; |
76 | }).pop(); | |
77 | } | |
78 | ||
79 | 20 | if (!node[first]) { |
80 | 20 | node[first] = { |
81 | _dataItems: [ ], | |
82 | _resourceConfig: jsonApi._resources[resourceAttribute], | |
83 | _filter: [ ] | |
84 | }; | |
85 | ||
86 | 20 | if (!((filter instanceof Array) && (filter.length === 0))) { |
87 | 20 | for (var i in filter) { |
88 | 2 | if (!(typeof filter[i] === "string" || (filter[i] instanceof Array))) continue; |
89 | 2 | node[first]._filter.push("filter[" + i + "]=" + filter[i]); |
90 | } | |
91 | } | |
92 | } | |
93 | 20 | iterate(rest, node[first], filter); |
94 | }; | |
95 | 12 | includes.forEach(function(include) { |
96 | 14 | iterate(include, tree, filters); |
97 | }); | |
98 | ||
99 | 12 | return callback(null, tree); |
100 | }; | |
101 | ||
102 | 1 | includePP._getDataItemsFromTree = function(tree) { |
103 | 32 | var items = tree._dataItems; |
104 | 32 | for (var i in tree) { |
105 | 104 | if (i[0] !== "_") { |
106 | 20 | items = items.concat(includePP._getDataItemsFromTree(tree[i])); |
107 | } | |
108 | } | |
109 | 32 | return items; |
110 | }; | |
111 | ||
112 | 1 | includePP._fillIncludeTree = function(includeTree, request, callback) { |
113 | /**** | |
114 | includeTree = { | |
115 | _dataItems: [ ], | |
116 | _filter: { }, | |
117 | _resourceConfig: { }, | |
118 | person: { includeTree }, | |
119 | booking: { includeTree } | |
120 | }; | |
121 | ****/ | |
122 | 32 | var includes = Object.keys(includeTree); |
123 | ||
124 | 32 | var map = { |
125 | primary: { }, | |
126 | foreign: { } | |
127 | }; | |
128 | 32 | includeTree._dataItems.forEach(function(dataItem) { |
129 | 69 | if (!dataItem) return [ ]; |
130 | 69 | return Object.keys(dataItem.relationships || { }).filter(function(keyName) { |
131 | 202 | return (keyName[0] !== "_") && (includes.indexOf(keyName) !== -1); |
132 | }).forEach(function(relation) { | |
133 | 47 | var someRelation = dataItem.relationships[relation]; |
134 | ||
135 | 47 | if (someRelation.meta.relation === "primary") { |
136 | 35 | var relationItems = someRelation.data; |
137 | 36 | if (!relationItems) return; |
138 | 60 | if (!(relationItems instanceof Array)) relationItems = [ relationItems ]; |
139 | 34 | relationItems.forEach(function(relationItem) { |
140 | 34 | var key = relationItem.type + "~~" + relation + "~~" + relation; |
141 | 34 | map.primary[key] = map.primary[key] || [ ]; |
142 | 34 | map.primary[key].push(relationItem.id); |
143 | }); | |
144 | } | |
145 | ||
146 | 46 | if (someRelation.meta.relation === "foreign") { |
147 | 12 | var key = someRelation.meta.as + "~~" + someRelation.meta.belongsTo + "~~" + relation; |
148 | 12 | map.foreign[key] = map.foreign[key] || [ ]; |
149 | 12 | map.foreign[key].push(dataItem.id); |
150 | } | |
151 | }); | |
152 | }); | |
153 | ||
154 | 32 | var resourcesToFetch = []; |
155 | ||
156 | 32 | Object.keys(map.primary).forEach(function(relation) { |
157 | 13 | var ids = _.uniq(map.primary[relation]); |
158 | 13 | var parts = relation.split("~~"); |
159 | 13 | var urlJoiner = "&filter[id]="; |
160 | 13 | ids = urlJoiner + ids.join(urlJoiner); |
161 | 13 | if (includeTree[parts[1]]._filter) { |
162 | 13 | ids += "&" + includeTree[parts[1]]._filter.join("&"); |
163 | } | |
164 | 13 | resourcesToFetch.push({ |
165 | url: jsonApi._apiConfig.pathPrefix + parts[0] + "/?" + ids, | |
166 | as: relation | |
167 | }); | |
168 | }); | |
169 | ||
170 | 32 | Object.keys(map.foreign).forEach(function(relation) { |
171 | 6 | var ids = _.uniq(map.foreign[relation]); |
172 | 6 | var parts = relation.split("~~"); |
173 | 6 | var urlJoiner = "&filter[" + parts[0] + "]="; |
174 | 6 | ids = urlJoiner + ids.join(urlJoiner); |
175 | 6 | if (includeTree[parts[2]]._filter) { |
176 | 6 | ids += "&" + includeTree[parts[2]]._filter.join("&"); |
177 | } | |
178 | 6 | resourcesToFetch.push({ |
179 | url: jsonApi._apiConfig.pathPrefix + parts[1] + "/?" + ids, | |
180 | as: relation | |
181 | }); | |
182 | }); | |
183 | ||
184 | 32 | async.map(resourcesToFetch, function(related, done) { |
185 | 19 | var parts = related.as.split("~~"); |
186 | 19 | debug.include(related); |
187 | ||
188 | 19 | rerouter.route({ |
189 | method: "GET", | |
190 | uri: related.url, | |
191 | originalRequest: request | |
192 | }, function(err, json) { | |
193 | 19 | if (err) { |
194 | 0 | debug.include("!!", JSON.stringify(err)); |
195 | 0 | return done(err.errors); |
196 | } | |
197 | ||
198 | 19 | var data = json.data; |
199 | 19 | if (!data) return done(); |
200 | 19 | if (!(data instanceof Array)) data = [ data ]; |
201 | 19 | includeTree[parts[2]]._dataItems = includeTree[parts[2]]._dataItems.concat(data); |
202 | 19 | return done(); |
203 | }); | |
204 | }, function(err) { | |
205 | 32 | if (err) return callback(err); |
206 | ||
207 | 32 | async.map(includes, function(include, done) { |
208 | 188 | if (include[0] === "_") return done(); |
209 | 20 | includePP._fillIncludeTree(includeTree[include], request, done); |
210 | }, callback); | |
211 | }); | |
212 | }; | |
213 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var sort = module.exports = { }; |
4 | ||
5 | 1 | sort.action = function(request, response, callback) { |
6 | 89 | var attribute = request.params.sort; |
7 | 89 | var ascending = 1; |
8 | 171 | if (!attribute) return callback(); |
9 | 7 | attribute = ("" + attribute); |
10 | 7 | if (attribute[0] === "-") { |
11 | 1 | ascending = -1; |
12 | 1 | attribute = attribute.substring(1, attribute.length); |
13 | } | |
14 | ||
15 | 7 | if (!request.resourceConfig.attributes[attribute]) { |
16 | 0 | return callback({ |
17 | status: "403", | |
18 | code: "EFORBIDDEN", | |
19 | title: "Invalid sort", | |
20 | detail: request.resourceConfig.resource + " do not have property " + attribute | |
21 | }); | |
22 | } | |
23 | ||
24 | 7 | response.data = response.data.sort(function(a, b) { |
25 | 7 | if (typeof a.attributes[attribute] === "string") { |
26 | 7 | return a.attributes[attribute].localeCompare(b.attributes[attribute]) * ascending; |
27 | 0 | } else if (typeof a.attributes[attribute] === "number") { |
28 | 0 | return (a.attributes[attribute] - b.attributes[attribute]) * ascending; |
29 | } else { | |
30 | 0 | return 0; |
31 | } | |
32 | }); | |
33 | ||
34 | 7 | return callback(); |
35 | }; | |
36 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var rerouter = module.exports = { }; |
4 | ||
5 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/./router.js"); |
6 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/./jsonApi.js"); |
7 | 1 | var url = require("qs"); |
8 | 1 | var _ = { |
9 | omit: require("lodash.omit") | |
10 | }; | |
11 | ||
12 | ||
13 | 1 | rerouter.route = function(newRequest, callback) { |
14 | 23 | var validRoutes = router._routes[newRequest.method.toLowerCase()]; |
15 | ||
16 | 23 | var path = rerouter._generateSanePath(newRequest); |
17 | 23 | var route = rerouter._pickFirstMatchingRoute(validRoutes, path); |
18 | ||
19 | 23 | var req = { |
20 | url: newRequest.uri, | |
21 | headers: newRequest.originalRequest.headers, | |
22 | cookies: newRequest.originalRequest.cookies, | |
23 | params: url.parse(newRequest.uri.split("?").pop()) | |
24 | }; | |
25 | 23 | rerouter._extendUrlParamsOntoReq(route, path, req); |
26 | ||
27 | 23 | var res = { |
28 | status: function(httpCode) { | |
29 | 23 | res.httpCode = httpCode; |
30 | 23 | return res; |
31 | }, | |
32 | json: function(payload) { | |
33 | 23 | var err = null; |
34 | 23 | if (res.httpCode >= 400) { |
35 | 0 | err = payload; |
36 | 0 | payload = undefined; |
37 | } | |
38 | 23 | return callback(err, payload); |
39 | } | |
40 | }; | |
41 | 23 | validRoutes[route](req, res, _.omit(newRequest.originalRequest, [ "params", "route" ])); |
42 | }; | |
43 | ||
44 | 1 | rerouter._generateSanePath = function(newRequest) { |
45 | 23 | var path = newRequest.uri; |
46 | 23 | if (path.match(/^https?\:\/\//)) { |
47 | 23 | path = path.split("/").slice(3).join("/"); |
48 | } | |
49 | 23 | if (jsonApi._apiConfig.base !== "/") { |
50 | 46 | if (path[0] !== "/") path = "/" + path; |
51 | 23 | path = path.split(jsonApi._apiConfig.base); |
52 | 23 | path.shift(); |
53 | 23 | path = path.join(jsonApi._apiConfig.base); |
54 | } | |
55 | 23 | path = path.replace(/^\//, "").split("?")[0].replace(/\/$/, ""); |
56 | 23 | return path; |
57 | }; | |
58 | ||
59 | 1 | rerouter._pickFirstMatchingRoute = function(validRoutes, path) { |
60 | 23 | return Object.keys(validRoutes).filter(function(someRoute) { |
61 | 138 | someRoute = someRoute.replace(/(\:[a-z]+)/g, "[^/]*?"); |
62 | 138 | someRoute = new RegExp("^" + someRoute); |
63 | 138 | return someRoute.test(path); |
64 | }).pop(); | |
65 | }; | |
66 | ||
67 | 1 | rerouter._extendUrlParamsOntoReq = function(route, path, req) { |
68 | 23 | route.split("/").forEach(function(urlPart, i) { |
69 | 23 | if (urlPart[0] !== ":") return; |
70 | 23 | req.params[urlPart.substring(1)] = path.split("/")[i]; |
71 | }); | |
72 | }; | |
73 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var responseHelper = module.exports = { }; |
4 | ||
5 | 1 | var _ = { |
6 | assign: require("lodash.assign"), | |
7 | pick: require("lodash.pick") | |
8 | }; | |
9 | 1 | var async = require("async"); |
10 | 1 | var pagination = require("/home/pmcnr/work/jsonapi-server/lib/./pagination.js"); |
11 | 1 | var Joi = require("joi"); |
12 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js"); |
13 | ||
14 | ||
15 | 1 | responseHelper.setBaseUrl = function(baseUrl) { |
16 | 1 | responseHelper._baseUrl = baseUrl; |
17 | }; | |
18 | 1 | responseHelper.setMetadata = function(meta) { |
19 | 1 | responseHelper._metadata = meta; |
20 | }; | |
21 | ||
22 | 1 | responseHelper._enforceSchemaOnArray = function(items, schema, callback) { |
23 | 66 | if (!(items instanceof Array)) { |
24 | 0 | items = [ items ]; |
25 | } | |
26 | 66 | async.map(items, function(item, done) { |
27 | 245 | return responseHelper._enforceSchemaOnObject(item, schema, done); |
28 | }, function(err, results) { | |
29 | 66 | if (err) return callback(err); |
30 | ||
31 | 66 | results = results.filter(function(result) { |
32 | 245 | return !!result; |
33 | }); | |
34 | 66 | return callback(null, results); |
35 | }); | |
36 | }; | |
37 | ||
38 | 1 | responseHelper._enforceSchemaOnObject = function(item, schema, callback) { |
39 | 266 | debug.validationOutput(JSON.stringify(item)); |
40 | 266 | Joi.validate(item, schema, function (err, sanitisedItem) { |
41 | 266 | if (err) { |
42 | 0 | debug.validationError(err.message, JSON.stringify(item)); |
43 | 0 | return callback(null, null); |
44 | } | |
45 | ||
46 | 266 | var dataItem = responseHelper._generateDataItem(sanitisedItem, schema); |
47 | 266 | return callback(null, dataItem); |
48 | }); | |
49 | }; | |
50 | ||
51 | 1 | responseHelper._generateDataItem = function(item, schema) { |
52 | ||
53 | 266 | var isSpecialProperty = function(value) { |
54 | 4620 | if (!(value instanceof Object)) return false; |
55 | 6312 | if (value._settings) return true; |
56 | 2928 | return false; |
57 | }; | |
58 | 266 | var linkProperties = Object.keys(schema).filter(function(someProperty) { |
59 | 2709 | return isSpecialProperty(schema[someProperty]); |
60 | }); | |
61 | 266 | var attributeProperties = Object.keys(schema).filter(function(someProperty) { |
62 | 2975 | if (someProperty === "id") return false; |
63 | 2709 | if (someProperty === "type") return false; |
64 | 2443 | if (someProperty === "meta") return false; |
65 | 1911 | return !isSpecialProperty(schema[someProperty]); |
66 | }); | |
67 | ||
68 | 266 | var result = { |
69 | type: item.type, | |
70 | id: item.id, | |
71 | attributes: _.pick(item, attributeProperties), | |
72 | links: responseHelper._generateLinks(item, schema, linkProperties), | |
73 | relationships: responseHelper._generateRelationships(item, schema, linkProperties), | |
74 | meta: item.meta | |
75 | }; | |
76 | ||
77 | 266 | return result; |
78 | }; | |
79 | ||
80 | 1 | responseHelper._generateLinks = function(item) { |
81 | 266 | return { |
82 | self: responseHelper._baseUrl + item.type + "/" + item.id | |
83 | }; | |
84 | }; | |
85 | ||
86 | 1 | responseHelper._generateRelationships = function(item, schema, linkProperties) { |
87 | 266 | if (linkProperties.length === 0) return undefined; |
88 | ||
89 | 266 | var links = { }; |
90 | ||
91 | 266 | linkProperties.forEach(function(linkProperty) { |
92 | 846 | links[linkProperty] = responseHelper._generateLink(item, schema[linkProperty], linkProperty); |
93 | }); | |
94 | ||
95 | 266 | return links; |
96 | }; | |
97 | ||
98 | 1 | responseHelper._generateLink = function(item, schemaProperty, linkProperty) { |
99 | 846 | var link = { |
100 | meta: { | |
101 | relation: "primary", | |
102 | // type: schemaProperty._settings.__many || schemaProperty._settings.__one, | |
103 | readOnly: false | |
104 | }, | |
105 | links: { | |
106 | self: responseHelper._baseUrl + item.type + "/" + item.id + "/relationships/" + linkProperty, | |
107 | related: responseHelper._baseUrl + item.type + "/" + item.id + "/" + linkProperty | |
108 | }, | |
109 | data: null | |
110 | }; | |
111 | ||
112 | 846 | if (schemaProperty._settings.__many) { |
113 | // $FlowFixMe: the data property can be either undefined (not present), null or [ ] | |
114 | 621 | link.data = [ ]; |
115 | 621 | var linkItems = item[linkProperty]; |
116 | 621 | if (linkItems) { |
117 | 423 | if (!(linkItems instanceof Array)) linkItems = [ linkItems ]; |
118 | 423 | linkItems.forEach(function(linkItem) { |
119 | 353 | link.data.push({ |
120 | type: linkItem.type, | |
121 | id: linkItem.id, | |
122 | meta: linkItem.meta | |
123 | }); | |
124 | }); | |
125 | } | |
126 | } | |
127 | ||
128 | 846 | if (schemaProperty._settings.__one) { |
129 | 225 | var linkItem = item[linkProperty]; |
130 | 225 | if (linkItem) { |
131 | // $FlowFixMe: the data property can be either undefined (not present), null or [ ] | |
132 | 202 | link.data = { |
133 | type: linkItem.type, | |
134 | id: linkItem.id, | |
135 | meta: linkItem.meta | |
136 | }; | |
137 | } | |
138 | } | |
139 | ||
140 | 846 | if (schemaProperty._settings.__as) { |
141 | 209 | var relatedResource = schemaProperty._settings.__one || schemaProperty._settings.__many; |
142 | // get information about the linkage - list of ids and types | |
143 | // /rest/bookings/relationships/?customer=26aa8a92-2845-4e40-999f-1fa006ec8c63 | |
144 | 209 | link.links.self = responseHelper._baseUrl + relatedResource + "/relationships/?" + schemaProperty._settings.__as + "=" + item.id; |
145 | // get full details of all linked resources | |
146 | // /rest/bookings/?filter[customer]=26aa8a92-2845-4e40-999f-1fa006ec8c63 | |
147 | 209 | link.links.related = responseHelper._baseUrl + relatedResource + "/?filter[" + schemaProperty._settings.__as + "]=" + item.id; |
148 | 209 | if (!item[linkProperty]) { |
149 | // $FlowFixMe: the data property can be either undefined (not present), null or [ ] | |
150 | 209 | link.data = undefined; |
151 | } | |
152 | 209 | link.meta = { |
153 | relation: "foreign", | |
154 | belongsTo: relatedResource, | |
155 | as: schemaProperty._settings.__as, | |
156 | many: !!schemaProperty._settings.__many, | |
157 | readOnly: true | |
158 | }; | |
159 | } | |
160 | ||
161 | 846 | return link; |
162 | }; | |
163 | ||
164 | 1 | responseHelper.generateError = function(request, err) { |
165 | 45 | debug.errors(request.route.verb, request.route.combined, JSON.stringify(err)); |
166 | 90 | if (!(err instanceof Array)) err = [ err ]; |
167 | ||
168 | 45 | var errorResponse = { |
169 | jsonapi: { | |
170 | version: "1.0" | |
171 | }, | |
172 | meta: responseHelper._generateMeta(request), | |
173 | links: { | |
174 | self: responseHelper._baseUrl + request.route.path | |
175 | }, | |
176 | errors: err.map(function(error) { | |
177 | 45 | return { |
178 | status: error.status, | |
179 | code: error.code, | |
180 | title: error.title, | |
181 | detail: error.detail | |
182 | }; | |
183 | }) | |
184 | }; | |
185 | ||
186 | 45 | return errorResponse; |
187 | }; | |
188 | ||
189 | 1 | responseHelper._generateResponse = function(request, resourceConfig, sanitisedData, handlerTotal) { |
190 | 93 | return { |
191 | jsonapi: { | |
192 | version: "1.0" | |
193 | }, | |
194 | meta: responseHelper._generateMeta(request, handlerTotal), | |
195 | links: _.assign({ | |
196 | self: responseHelper._baseUrl + request.route.path + (request.route.query ? ("?" + request.route.query) : "") | |
197 | }, pagination.generatePageLinks(request, handlerTotal)), | |
198 | data: sanitisedData | |
199 | }; | |
200 | }; | |
201 | ||
202 | 1 | responseHelper._generateMeta = function(request, handlerTotal) { |
203 | 139 | var meta = _.assign({ }, responseHelper._metadata); |
204 | ||
205 | 139 | if (handlerTotal) { |
206 | 66 | meta.page = pagination.generateMetaSummary(request, handlerTotal); |
207 | } | |
208 | ||
209 | 139 | return meta; |
210 | }; | |
211 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var router = module.exports = { }; |
4 | ||
5 | 1 | var _ = { |
6 | assign: require("lodash.assign"), | |
7 | omit: require("lodash.omit") | |
8 | }; | |
9 | 1 | var express = require("express"); |
10 | 1 | var app = express(); |
11 | 1 | var server; |
12 | 1 | var bodyParser = require("body-parser"); |
13 | 1 | var cookieParser = require("cookie-parser"); |
14 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/./jsonApi.js"); |
15 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/./debugging.js"); |
16 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/./responseHelper.js"); |
17 | 1 | var url = require("url"); |
18 | ||
19 | 1 | app.use(function(req, res, next) { |
20 | 212 | if (!req.headers["content-type"] && !req.headers.accept) return next(); |
21 | ||
22 | 22 | if (req.headers["content-type"]) { |
23 | // 415 Unsupported Media Type | |
24 | 21 | if (req.headers["content-type"].match(/^application\/vnd\.api\+json;.+$/)) { |
25 | 1 | return res.status(415).end(); |
26 | } | |
27 | ||
28 | // Convert "application/vnd.api+json" content type to "application/json". | |
29 | // This enables the express body parser to correctly parse the JSON payload. | |
30 | 20 | if (req.headers["content-type"].match(/^application\/vnd\.api\+json$/)) { |
31 | 20 | req.headers["content-type"] = "application/json"; |
32 | } | |
33 | } | |
34 | ||
35 | 21 | if (req.headers.accept) { |
36 | // 406 Not Acceptable | |
37 | 1 | var matchingTypes = req.headers.accept.split(/, ?/); |
38 | 1 | matchingTypes = matchingTypes.filter(function(mediaType) { |
39 | // Accept application/*, */vnd.api+json, */* and the correct JSON:API type. | |
40 | 3 | return mediaType.match(/^(\*|application)\/(\*|vnd\.api\+json)$/) || mediaType.match(/\*\/\*/); |
41 | }); | |
42 | ||
43 | 1 | if (matchingTypes.length === 0) { |
44 | 1 | return res.status(406).end(); |
45 | } | |
46 | } | |
47 | ||
48 | 20 | return next(); |
49 | }); | |
50 | ||
51 | 1 | app.use(function(req, res, next) { |
52 | 115 | res.set({ |
53 | "Content-Type": "application/vnd.api+json", | |
54 | "Access-Control-Allow-Origin": "*", | |
55 | "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS", | |
56 | "Access-Control-Allow-Headers": req.headers["access-control-request-headers"] || "", | |
57 | "Cache-Control": "private, must-revalidate, max-age=0", | |
58 | "Expires": "Thu, 01 Jan 1970 00:00:00" | |
59 | }); | |
60 | ||
61 | 115 | if (req.method === "OPTIONS") { |
62 | 1 | return res.status(204).end(); |
63 | } | |
64 | ||
65 | 114 | return next(); |
66 | }); | |
67 | ||
68 | 1 | app.use(bodyParser.json()); |
69 | 1 | app.use(bodyParser.urlencoded({ extended: true })); |
70 | 1 | app.use(cookieParser()); |
71 | 1 | app.disable("x-powered-by"); |
72 | 1 | app.disable("etag"); |
73 | ||
74 | 1 | var requestId = 0; |
75 | 1 | app.route("*").all(function(req, res, next) { |
76 | 114 | debug.requestCounter(requestId++, req.method, req.url); |
77 | 114 | if (requestId > 1000) requestId = 0; |
78 | 114 | next(); |
79 | }); | |
80 | ||
81 | 1 | router.listen = function(port) { |
82 | ||
83 | 20 | if (!server) { |
84 | 19 | if (jsonApi._apiConfig.protocol === "https") { |
85 | 0 | server = require("https").createServer(jsonApi._apiConfig.tls || {}, app); |
86 | } else { | |
87 | 19 | server = require("http").createServer(app); |
88 | } | |
89 | 19 | server.listen(port); |
90 | } | |
91 | }; | |
92 | ||
93 | 1 | router.close = function() { |
94 | 19 | server.close(); |
95 | 19 | server = null; |
96 | }; | |
97 | ||
98 | 1 | router._routes = { }; |
99 | 1 | router.bindRoute = function(config, callback) { |
100 | 240 | var path = jsonApi._apiConfig.base + config.path; |
101 | 240 | var verb = config.verb.toLowerCase(); |
102 | ||
103 | 240 | var routeHandler = function(req, res, extras) { |
104 | 135 | var request = router._getParams(req); |
105 | 135 | request = _.assign(request, extras); |
106 | 135 | var resourceConfig = jsonApi._resources[request.params.type]; |
107 | 135 | request.resourceConfig = resourceConfig; |
108 | 135 | router.authenticate(request, res, function() { |
109 | 133 | return callback(request, resourceConfig, res); |
110 | }); | |
111 | }; | |
112 | 240 | router._routes[verb] = router._routes[verb] || { }; |
113 | 240 | router._routes[verb][config.path] = routeHandler; |
114 | 240 | app[verb](path, routeHandler); |
115 | }; | |
116 | ||
117 | 1 | router.authenticate = function(request, res, callback) { |
118 | 135 | if (!router._authFunction) return callback(); |
119 | ||
120 | 135 | router._authFunction(request, function(err) { |
121 | 268 | if (!err) return callback(); |
122 | ||
123 | 2 | var errorWrapper = { |
124 | status: "401", | |
125 | code: "UNAUTHORIZED", | |
126 | title: "Authentication Failed", | |
127 | detail: err || "You are not authorised to access this resource." | |
128 | }; | |
129 | 2 | var payload = responseHelper.generateError(request, errorWrapper); |
130 | 2 | res.status(401).json(payload); |
131 | }); | |
132 | }; | |
133 | ||
134 | 1 | router.authenticateWith = function(authFunction) { |
135 | 1 | router._authFunction = authFunction; |
136 | }; | |
137 | ||
138 | 1 | router.bind404 = function(callback) { |
139 | 20 | app.use(function(req, res) { |
140 | 2 | var request = router._getParams(req); |
141 | 2 | return callback(request, res); |
142 | }); | |
143 | }; | |
144 | ||
145 | 1 | router.bindErrorHandler = function(callback) { |
146 | 20 | app.use(function(error, req, res, next) { |
147 | 0 | var request = router._getParams(req); |
148 | 0 | return callback(request, res, error, next); |
149 | }); | |
150 | }; | |
151 | ||
152 | 1 | router._getParams = function(req) { |
153 | 137 | var urlParts = req.url.split(jsonApi._apiConfig.base); |
154 | 137 | urlParts.shift(); |
155 | 137 | urlParts = urlParts.join(jsonApi._apiConfig.base).split("?"); |
156 | ||
157 | 137 | var headersToRemove = [ |
158 | "host", "connection", "accept-encoding", "accept-language", "content-length" | |
159 | ]; | |
160 | ||
161 | 137 | return { |
162 | params: _.assign(req.params, req.body, req.query), | |
163 | headers: req.headers, | |
164 | safeHeaders: _.omit(req.headers, headersToRemove), | |
165 | cookies: req.cookies, | |
166 | route: { | |
167 | verb: req.method, | |
168 | host: req.headers.host, | |
169 | base: jsonApi._apiConfig.base, | |
170 | path: urlParts.shift() || "", | |
171 | query: urlParts.shift() || "", | |
172 | combined: url.format({ | |
173 | protocol: jsonApi._apiConfig.protocol, | |
174 | hostname: jsonApi._apiConfig.hostname, | |
175 | port: jsonApi._apiConfig.port | |
176 | }) + req.url | |
177 | } | |
178 | }; | |
179 | }; | |
180 | ||
181 | 1 | router.sendResponse = function(res, payload, httpCode) { |
182 | 135 | res.status(httpCode).json(payload); |
183 | }; | |
184 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var foreignKeySearchRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
9 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
10 | ||
11 | ||
12 | 1 | foreignKeySearchRoute.register = function() { |
13 | 20 | router.bindRoute({ |
14 | verb: "get", | |
15 | path: ":type/relationships/?" | |
16 | }, function(request, resourceConfig, res) { | |
17 | 3 | var foreignKey; |
18 | 3 | var searchResults; |
19 | 3 | var response; |
20 | ||
21 | 3 | async.waterfall([ |
22 | function(callback) { | |
23 | 3 | helper.verifyRequest(request, resourceConfig, res, "search", callback); |
24 | }, | |
25 | function(callback) { | |
26 | 2 | foreignKey = Object.keys(request.params).filter(function(param) { |
27 | 4 | return ["include", "type", "sort", "filter", "fields", "requestId"].indexOf(param) === -1; |
28 | }).pop(); | |
29 | 2 | request.params.relationships = { }; |
30 | 2 | request.params.relationships[foreignKey] = request.params[foreignKey]; |
31 | 2 | delete request.params[foreignKey]; |
32 | 2 | callback(); |
33 | }, | |
34 | function(callback) { | |
35 | 2 | var foreignKeySchema = resourceConfig.attributes[foreignKey]; |
36 | 2 | if (!foreignKeySchema || !foreignKeySchema._settings) { |
37 | 1 | return callback({ |
38 | status: "403", | |
39 | code: "EFORBIDDEN", | |
40 | title: "Invalid foreign key lookup", | |
41 | detail: "Relation [" + foreignKey + "] does not exist within " + request.params.type | |
42 | }); | |
43 | } | |
44 | 1 | if (!(foreignKeySchema._settings.__one || foreignKeySchema._settings.__many)) { |
45 | 0 | return callback({ |
46 | status: "403", | |
47 | code: "EFORBIDDEN", | |
48 | title: "Invalid foreign key lookup", | |
49 | detail: "Attribute [" + foreignKey + "] does not represent a relation within " + request.params.type | |
50 | }); | |
51 | } | |
52 | 1 | callback(); |
53 | }, | |
54 | function(callback) { | |
55 | 1 | resourceConfig.handlers.search(request, callback); |
56 | }, | |
57 | function(results, pageData, callback) { | |
58 | 1 | searchResults = results.map(function(result) { |
59 | 4 | return { |
60 | id: result.id, | |
61 | type: result.type | |
62 | }; | |
63 | }); | |
64 | 1 | if (resourceConfig.attributes[foreignKey]) { |
65 | 1 | searchResults = searchResults[0] || null; |
66 | } | |
67 | 1 | callback(); |
68 | }, | |
69 | function(callback) { | |
70 | 1 | response = responseHelper._generateResponse(request, resourceConfig, searchResults); |
71 | 1 | response.included = [ ]; |
72 | 1 | postProcess.handle(request, response, callback); |
73 | } | |
74 | ], function(err) { | |
75 | 3 | if (err) return helper.handleError(request, res, err); |
76 | 1 | return router.sendResponse(res, response, 200); |
77 | }); | |
78 | }); | |
79 | }; | |
80 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var swagger = module.exports = { }; |
4 | ||
5 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
6 | 1 | var swaggerGenerator = require("/home/pmcnr/work/jsonapi-server/lib/routes/../swagger"); |
7 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/routes/../../"); |
8 | ||
9 | ||
10 | 1 | swagger.register = function() { |
11 | 20 | if (!jsonApi._apiConfig.swagger) return; |
12 | ||
13 | 20 | router.bindRoute({ |
14 | verb: "get", | |
15 | path: "swagger.json" | |
16 | }, function(request, resourceConfig, res) { | |
17 | 0 | if (!swagger._cache) { |
18 | 0 | swagger._cache = swaggerGenerator.generateDocumentation(); |
19 | } | |
20 | ||
21 | 0 | return res.json(swagger._cache); |
22 | }); | |
23 | }; | |
24 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var addRelationRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
9 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
10 | ||
11 | ||
12 | 1 | addRelationRoute.register = function() { |
13 | 20 | router.bindRoute({ |
14 | verb: "post", | |
15 | path: ":type/:id/relationships/:relation" | |
16 | }, function(request, resourceConfig, res) { | |
17 | 4 | var newResource; |
18 | 4 | var theirResource; |
19 | 4 | var response; |
20 | ||
21 | 4 | async.waterfall([ |
22 | function(callback) { | |
23 | 4 | helper.verifyRequest(request, resourceConfig, res, "update", callback); |
24 | }, | |
25 | function(callback) { | |
26 | 3 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
27 | }, | |
28 | function(callback) { | |
29 | 3 | helper.checkForBody(request, callback); |
30 | }, | |
31 | function(callback) { | |
32 | 3 | resourceConfig.handlers.find(request, callback); |
33 | }, | |
34 | function(ourResource, callback) { | |
35 | 2 | theirResource = JSON.parse(JSON.stringify(ourResource)); |
36 | ||
37 | 2 | var theirs = request.params.data; |
38 | 2 | theirResource[request.params.relation] = theirResource[request.params.relation] || [ ]; |
39 | 2 | theirResource[request.params.relation].push(theirs); |
40 | 2 | helper.validate(theirResource, resourceConfig.onCreate, callback); |
41 | }, | |
42 | function(callback) { | |
43 | 1 | resourceConfig.handlers.update(request, theirResource, callback); |
44 | }, | |
45 | function(result, callback) { | |
46 | 1 | resourceConfig.handlers.find(request, callback); |
47 | }, | |
48 | function(result, callback) { | |
49 | 1 | newResource = result; |
50 | 1 | postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback); |
51 | }, | |
52 | function(callback) { | |
53 | 1 | responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback); |
54 | }, | |
55 | function(sanitisedData, callback) { | |
56 | 1 | sanitisedData = sanitisedData.relationships[request.params.relation].data; |
57 | 1 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
58 | 1 | postProcess.handle(request, response, callback); |
59 | } | |
60 | ], function(err) { | |
61 | 5 | if (err) return helper.handleError(request, res, err); |
62 | 1 | router.sendResponse(res, response, 201); |
63 | }); | |
64 | }); | |
65 | }; | |
66 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var createRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var _ = { |
7 | assign: require("lodash.assign") | |
8 | }; | |
9 | 1 | var uuid = require("node-uuid"); |
10 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
11 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
12 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
13 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
14 | ||
15 | ||
16 | 1 | createRoute.register = function() { |
17 | 20 | router.bindRoute({ |
18 | verb: "post", | |
19 | path: ":type" | |
20 | }, function(request, resourceConfig, res) { | |
21 | 4 | var theirResource; |
22 | 4 | var newResource; |
23 | 4 | var response; |
24 | ||
25 | 4 | async.waterfall([ |
26 | function(callback) { | |
27 | 4 | helper.verifyRequest(request, resourceConfig, res, "create", callback); |
28 | }, | |
29 | function(callback) { | |
30 | 3 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
31 | }, | |
32 | function(callback) { | |
33 | 3 | helper.checkForBody(request, callback); |
34 | }, | |
35 | function(callback) { | |
36 | 2 | var theirs = request.params.data; |
37 | 2 | theirResource = _.assign({ |
38 | id: uuid.v4(), | |
39 | type: request.params.type | |
40 | }, theirs.attributes, { meta: theirs.meta }); | |
41 | 2 | for (var i in theirs.relationships) { |
42 | 1 | theirResource[i] = theirs.relationships[i].data; |
43 | } | |
44 | 2 | helper.validate(theirResource, resourceConfig.onCreate, callback); |
45 | }, | |
46 | function(callback) { | |
47 | 1 | resourceConfig.handlers.create(request, theirResource, callback); |
48 | }, | |
49 | function(result, callback) { | |
50 | 1 | newResource = result; |
51 | 1 | request.params.id = newResource.id; |
52 | 1 | resourceConfig.handlers.find(request, callback); |
53 | }, | |
54 | function(result, callback) { | |
55 | 1 | newResource = result; |
56 | 1 | postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback); |
57 | }, | |
58 | function(callback) { | |
59 | 1 | responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback); |
60 | }, | |
61 | function(sanitisedData, callback) { | |
62 | 1 | request.route.path += "/" + newResource.id; |
63 | 1 | res.set({ |
64 | "Location": request.route.combined + "/" + newResource.id | |
65 | }); | |
66 | 1 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
67 | 1 | postProcess.handle(request, response, callback); |
68 | } | |
69 | ], function(err) { | |
70 | 5 | if (err) return helper.handleError(request, res, err); |
71 | 1 | return router.sendResponse(res, response, 201); |
72 | }); | |
73 | }); | |
74 | }; | |
75 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var deleteRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
9 | ||
10 | ||
11 | 1 | deleteRoute.register = function() { |
12 | 20 | router.bindRoute({ |
13 | verb: "delete", | |
14 | path: ":type/:id" | |
15 | }, function(request, resourceConfig, res) { | |
16 | 4 | async.waterfall([ |
17 | function(callback) { | |
18 | 4 | helper.verifyRequest(request, resourceConfig, res, "delete", callback); |
19 | }, | |
20 | function(callback) { | |
21 | 2 | resourceConfig.handlers.delete(request, callback); |
22 | } | |
23 | ], function(err) { | |
24 | 3 | if (err) return helper.handleError(request, res, err); |
25 | ||
26 | 1 | var response = { |
27 | meta: responseHelper._generateMeta(request) | |
28 | }; | |
29 | 1 | router.sendResponse(res, response, 200); |
30 | }); | |
31 | }); | |
32 | }; | |
33 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var findRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var filter = require("/home/pmcnr/work/jsonapi-server/lib/routes/../filter.js"); |
9 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
10 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
11 | ||
12 | ||
13 | 1 | findRoute.register = function() { |
14 | 20 | router.bindRoute({ |
15 | verb: "get", | |
16 | path: ":type/:id" | |
17 | }, function(request, resourceConfig, res) { | |
18 | 13 | var resource; |
19 | 13 | var response; |
20 | ||
21 | 13 | async.waterfall([ |
22 | function(callback) { | |
23 | 13 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
24 | }, | |
25 | function parseAndValidateFilter(callback) { | |
26 | 12 | return callback(filter.parseAndValidate(request)); |
27 | }, | |
28 | function(callback) { | |
29 | 12 | resourceConfig.handlers.find(request, callback); |
30 | }, | |
31 | function(result, callback) { | |
32 | 10 | resource = result; |
33 | 10 | postProcess.fetchForeignKeys(request, resource, resourceConfig.attributes, callback); |
34 | }, | |
35 | function(callback) { | |
36 | 10 | responseHelper._enforceSchemaOnObject(resource, resourceConfig.attributes, callback); |
37 | }, | |
38 | function(sanitisedData, callback) { | |
39 | 10 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
40 | 10 | response.included = [ ]; |
41 | 10 | postProcess.handle(request, response, callback); |
42 | } | |
43 | ], function(err) { | |
44 | 14 | if (err) return helper.handleError(request, res, err); |
45 | 10 | return router.sendResponse(res, response, 200); |
46 | }); | |
47 | }); | |
48 | }; | |
49 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var helper = module.exports = { }; |
4 | ||
5 | 1 | var Joi = require("joi"); |
6 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var debug = require("/home/pmcnr/work/jsonapi-server/lib/routes/../debugging.js"); |
9 | 1 | var _ = { |
10 | assign: require("lodash.assign") | |
11 | }; | |
12 | ||
13 | ||
14 | 1 | helper.validate = function(someObject, someDefinition, callback) { |
15 | 88 | debug.validationInput(JSON.stringify(someObject)); |
16 | 88 | Joi.validate(someObject, someDefinition, { abortEarly: false }, function (err, sanitisedObject) { |
17 | 88 | if (err) { |
18 | 8 | return callback({ |
19 | status: "403", | |
20 | code: "EFORBIDDEN", | |
21 | title: "Param validation failed", | |
22 | detail: err.details | |
23 | }); | |
24 | } | |
25 | 80 | _.assign(someObject, sanitisedObject); |
26 | 80 | callback(); |
27 | }); | |
28 | }; | |
29 | ||
30 | 1 | helper.checkForBody = function(request, callback) { |
31 | 21 | if (!request.params.data) { |
32 | 1 | return callback({ |
33 | status: "403", | |
34 | code: "EFORBIDDEN", | |
35 | title: "Request validation failed", | |
36 | detail: "Missing \"data\" - have you sent the right http headers?" | |
37 | }); | |
38 | } | |
39 | 20 | callback(); |
40 | }; | |
41 | ||
42 | 1 | helper.handleError = function(request, res, err) { |
43 | 43 | var errorResponse = responseHelper.generateError(request, err); |
44 | 43 | var httpCode = errorResponse.errors[0].status || 500; |
45 | 43 | return router.sendResponse(res, errorResponse, httpCode); |
46 | }; | |
47 | ||
48 | 1 | helper.verifyRequest = function(request, resourceConfig, res, handlerRequest, callback) { |
49 | 154 | if (!resourceConfig) { |
50 | 8 | return helper.handleError(request, res, { |
51 | status: "404", | |
52 | code: "ENOTFOUND", | |
53 | title: "Resource not found", | |
54 | detail: "The requested resource '" + request.params.type + "' does not exist" | |
55 | }); | |
56 | } | |
57 | ||
58 | 146 | if (!resourceConfig.handlers.ready) { |
59 | 1 | return helper.handleError(request, res, { |
60 | status: "503", | |
61 | code: "EUNAVAILABLE", | |
62 | title: "Resource temporarily unavailable", | |
63 | detail: "The requested resource '" + request.params.type + "' is temporarily unavailable" | |
64 | }); | |
65 | } | |
66 | ||
67 | 145 | if (!resourceConfig.handlers[handlerRequest]) { |
68 | 1 | return helper.handleError(request, res, { |
69 | status: "403", | |
70 | code: "EFORBIDDEN", | |
71 | title: "Resource not supported", | |
72 | detail: "The requested resource '" + request.params.type + "' does not support '" + handlerRequest + "'" | |
73 | }); | |
74 | } | |
75 | ||
76 | 144 | return callback(); |
77 | }; | |
78 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var routes = module.exports = { }; |
4 | ||
5 | 1 | var fs = require("fs"); |
6 | 1 | var path = require("path"); |
7 | ||
8 | ||
9 | 1 | routes.handlers = { }; |
10 | 1 | fs.readdirSync(__dirname).filter(function(filename) { |
11 | 16 | return /\.js$/.test(filename) && (filename !== "index.js") && (filename !== "helper.js"); |
12 | }).sort().forEach(function(filename) { | |
13 | 14 | routes.handlers[filename] = require(path.join(__dirname, filename)); |
14 | }); | |
15 | ||
16 | 1 | routes.register = function() { |
17 | 20 | for (var i in routes.handlers) { |
18 | 280 | routes.handlers[i].register(); |
19 | } | |
20 | }; | |
21 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var relatedRoute = module.exports = { }; |
4 | ||
5 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/routes/../jsonApi.js"); |
6 | 1 | var async = require("async"); |
7 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
8 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
9 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
10 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
11 | ||
12 | ||
13 | 1 | relatedRoute.register = function() { |
14 | 20 | router.bindRoute({ |
15 | verb: "get", | |
16 | path: ":type/:id/:relation" | |
17 | }, function(request, resourceConfig, res) { | |
18 | 8 | var relation; |
19 | 8 | var mainResource; |
20 | 8 | var relatedResources; |
21 | 8 | var response; |
22 | ||
23 | 8 | async.waterfall([ |
24 | function(callback) { | |
25 | 8 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
26 | }, | |
27 | function(callback) { | |
28 | 8 | relation = resourceConfig.attributes[request.params.relation]; |
29 | 8 | if (!relation || !relation._settings || !(relation._settings.__one || relation._settings.__many)) { |
30 | 1 | return callback({ |
31 | status: "404", | |
32 | code: "ENOTFOUND", | |
33 | title: "Resource not found", | |
34 | detail: "The requested relation does not exist within the requested type" | |
35 | }); | |
36 | } | |
37 | 7 | if (relation._settings.__as) { |
38 | 1 | return callback({ |
39 | status: "404", | |
40 | code: "EFOREIGN", | |
41 | title: "Relation is Foreign", | |
42 | detail: "The requested relation is a foreign relation and cannot be accessed in this manner." | |
43 | }); | |
44 | } | |
45 | 6 | callback(); |
46 | }, | |
47 | function(callback) { | |
48 | 6 | resourceConfig.handlers.find(request, callback); |
49 | }, | |
50 | function(result, callback) { | |
51 | 5 | mainResource = result; |
52 | 5 | postProcess._fetchRelatedResources(request, mainResource, callback); |
53 | }, | |
54 | function(newResources, callback) { | |
55 | 5 | relatedResources = newResources; |
56 | 5 | if (relation._settings.__one) { |
57 | 5 | relatedResources = relatedResources[0]; |
58 | } | |
59 | 5 | request.resourceConfig = jsonApi._resources[relation._settings.__one || relation._settings.__many]; |
60 | 5 | response = responseHelper._generateResponse(request, resourceConfig, relatedResources); |
61 | 5 | if (relatedResources !== null) { |
62 | 4 | response.included = [ ]; |
63 | } | |
64 | 5 | postProcess.handle(request, response, callback); |
65 | } | |
66 | ], function(err) { | |
67 | 11 | if (err) return helper.handleError(request, res, err); |
68 | 5 | return router.sendResponse(res, response, 200); |
69 | }); | |
70 | }); | |
71 | }; | |
72 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var relationshipsRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
9 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
10 | ||
11 | ||
12 | 1 | relationshipsRoute.register = function() { |
13 | 20 | router.bindRoute({ |
14 | verb: "get", | |
15 | path: ":type/:id/relationships/:relation" | |
16 | }, function(request, resourceConfig, res) { | |
17 | 6 | var resource; |
18 | 6 | var response; |
19 | ||
20 | 6 | async.waterfall([ |
21 | function(callback) { | |
22 | 6 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
23 | }, | |
24 | function(callback) { | |
25 | 6 | var relation = resourceConfig.attributes[request.params.relation]; |
26 | 6 | if (!relation || !(relation._settings.__one || relation._settings.__many)) { |
27 | 1 | return callback({ |
28 | status: "404", | |
29 | code: "ENOTFOUND", | |
30 | title: "Resource not found", | |
31 | detail: "The requested relation does not exist within the requested type" | |
32 | }); | |
33 | } | |
34 | 5 | callback(); |
35 | }, | |
36 | function(callback) { | |
37 | 5 | resourceConfig.handlers.find(request, callback); |
38 | }, | |
39 | function(result, callback) { | |
40 | 4 | resource = result; |
41 | 4 | postProcess.fetchForeignKeys(request, resource, resourceConfig.attributes, callback); |
42 | }, | |
43 | function(callback) { | |
44 | 4 | responseHelper._enforceSchemaOnObject(resource, resourceConfig.attributes, callback); |
45 | }, | |
46 | function(sanitisedData, callback) { | |
47 | 4 | sanitisedData = sanitisedData.relationships[request.params.relation].data; |
48 | 4 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
49 | 4 | callback(); |
50 | } | |
51 | ], function(err) { | |
52 | 8 | if (err) return helper.handleError(request, res, err); |
53 | 4 | return router.sendResponse(res, response, 200); |
54 | }); | |
55 | }); | |
56 | }; | |
57 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var removeRelationRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
9 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
10 | ||
11 | ||
12 | 1 | removeRelationRoute.register = function() { |
13 | 20 | router.bindRoute({ |
14 | verb: "delete", | |
15 | path: ":type/:id/relationships/:relation" | |
16 | }, function(request, resourceConfig, res) { | |
17 | 5 | var newResource; |
18 | 5 | var theirResource; |
19 | 5 | var response; |
20 | ||
21 | 5 | async.waterfall([ |
22 | function(callback) { | |
23 | 5 | helper.verifyRequest(request, resourceConfig, res, "update", callback); |
24 | }, | |
25 | function(callback) { | |
26 | 4 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
27 | }, | |
28 | function(callback) { | |
29 | 4 | helper.checkForBody(request, callback); |
30 | }, | |
31 | function(callback) { | |
32 | 4 | resourceConfig.handlers.find(request, callback); |
33 | }, | |
34 | function(ourResource, callback) { | |
35 | 3 | theirResource = ourResource; |
36 | ||
37 | 3 | var theirs = request.params.data; |
38 | 3 | theirResource[request.params.relation] = theirResource[request.params.relation] || [ ]; |
39 | 6 | if (!(theirs instanceof Array)) theirs = [ theirs ]; |
40 | ||
41 | 3 | var keys = theirResource[request.params.relation].map(function(j) { |
42 | 6 | return j.id; |
43 | }); | |
44 | ||
45 | 3 | for (var i = 0; i < theirs.length; i++) { |
46 | 3 | if (theirs[i].type !== request.params.relation) { |
47 | 1 | return callback({ |
48 | status: "403", | |
49 | code: "EFORBIDDEN", | |
50 | title: "Invalid Request", | |
51 | detail: "Invalid type " + theirs[i].type | |
52 | }); | |
53 | } | |
54 | 2 | var someId = theirs[i].id; |
55 | 2 | var indexOfTheirs = keys.indexOf(someId); |
56 | 2 | if (indexOfTheirs === -1) { |
57 | 1 | return callback({ |
58 | status: "403", | |
59 | code: "EFORBIDDEN", | |
60 | title: "Invalid Request", | |
61 | detail: "Unknown id " + someId | |
62 | }); | |
63 | } | |
64 | 1 | theirResource[request.params.relation].splice(indexOfTheirs, 1); |
65 | } | |
66 | ||
67 | 1 | helper.validate(theirResource, resourceConfig.onCreate, callback); |
68 | }, | |
69 | function(callback) { | |
70 | 1 | resourceConfig.handlers.update(request, theirResource, callback); |
71 | }, | |
72 | function(result, callback) { | |
73 | 1 | resourceConfig.handlers.find(request, callback); |
74 | }, | |
75 | function(result, callback) { | |
76 | 1 | newResource = result; |
77 | 1 | postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback); |
78 | }, | |
79 | function(callback) { | |
80 | 1 | responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback); |
81 | }, | |
82 | function(sanitisedData, callback) { | |
83 | 1 | sanitisedData = sanitisedData.relationships[request.params.relation].data; |
84 | 1 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
85 | 1 | postProcess.handle(request, response, callback); |
86 | } | |
87 | ], function(err) { | |
88 | 7 | if (err) return helper.handleError(request, res, err); |
89 | 1 | router.sendResponse(res, response, 200); |
90 | }); | |
91 | }); | |
92 | }; | |
93 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var searchRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | 1 | var filter = require("/home/pmcnr/work/jsonapi-server/lib/routes/../filter.js"); |
9 | 1 | var pagination = require("/home/pmcnr/work/jsonapi-server/lib/routes/../pagination.js"); |
10 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
11 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
12 | ||
13 | ||
14 | 1 | searchRoute.register = function() { |
15 | 20 | router.bindRoute({ |
16 | verb: "get", | |
17 | path: ":type" | |
18 | }, function(request, resourceConfig, res) { | |
19 | 73 | var searchResults; |
20 | 73 | var response; |
21 | 73 | var paginationInfo; |
22 | ||
23 | 73 | async.waterfall([ |
24 | function(callback) { | |
25 | 73 | helper.verifyRequest(request, resourceConfig, res, "search", callback); |
26 | }, | |
27 | function(callback) { | |
28 | 72 | helper.validate(request.params, resourceConfig.searchParams, callback); |
29 | }, | |
30 | function parseAndValidateFilter(callback) { | |
31 | 71 | return callback(filter.parseAndValidate(request)); |
32 | }, | |
33 | function validatePaginationParams(callback) { | |
34 | 66 | pagination.validatePaginationParams(request); |
35 | 66 | return callback(); |
36 | }, | |
37 | function(callback) { | |
38 | 66 | resourceConfig.handlers.search(request, callback); |
39 | }, | |
40 | function enforcePagination(results, pageInfo, callback) { | |
41 | 66 | searchResults = pagination.enforcePagination(request, results); |
42 | 66 | paginationInfo = pageInfo; |
43 | 66 | return callback(); |
44 | }, | |
45 | function(callback) { | |
46 | 66 | postProcess.fetchForeignKeys(request, searchResults, resourceConfig.attributes, callback); |
47 | }, | |
48 | function(callback) { | |
49 | 66 | responseHelper._enforceSchemaOnArray(searchResults, resourceConfig.attributes, callback); |
50 | }, | |
51 | function(sanitisedData, callback) { | |
52 | 66 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData, paginationInfo); |
53 | 66 | response.included = [ ]; |
54 | 66 | postProcess.handle(request, response, callback); |
55 | } | |
56 | ], function(err) { | |
57 | 80 | if (err) return helper.handleError(request, res, err); |
58 | 64 | return router.sendResponse(res, response, 200); |
59 | }); | |
60 | }); | |
61 | }; | |
62 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var updateRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var _ = { |
7 | assign: require("lodash.assign"), | |
8 | pick: require("lodash.pick") | |
9 | }; | |
10 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
11 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
12 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
13 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
14 | ||
15 | ||
16 | 1 | updateRoute.register = function() { |
17 | 20 | router.bindRoute({ |
18 | verb: "patch", | |
19 | path: ":type/:id" | |
20 | }, function(request, resourceConfig, res) { | |
21 | 9 | var theirResource; |
22 | 9 | var newResource; |
23 | 9 | var response; |
24 | ||
25 | 9 | async.waterfall([ |
26 | function(callback) { | |
27 | 9 | helper.verifyRequest(request, resourceConfig, res, "update", callback); |
28 | }, | |
29 | function(callback) { | |
30 | 8 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
31 | }, | |
32 | function(callback) { | |
33 | 8 | helper.checkForBody(request, callback); |
34 | }, | |
35 | function(callback) { | |
36 | 8 | var theirs = request.params.data; |
37 | 8 | theirResource = _.assign({ |
38 | id: request.params.id, | |
39 | type: request.params.type | |
40 | }, theirs.attributes, { meta: theirs.meta }); | |
41 | 8 | for (var i in theirs.relationships) { |
42 | 5 | theirResource[i] = theirs.relationships[i].data; |
43 | } | |
44 | 8 | callback(); |
45 | }, | |
46 | function(callback) { | |
47 | 8 | var validationObject = _.pick(resourceConfig.onCreate, Object.keys(theirResource)); |
48 | 8 | helper.validate(theirResource, validationObject, callback); |
49 | }, | |
50 | function(callback) { | |
51 | 4 | resourceConfig.handlers.update(request, theirResource, callback); |
52 | }, | |
53 | function(result, callback) { | |
54 | 3 | resourceConfig.handlers.find(request, callback); |
55 | }, | |
56 | function(result, callback) { | |
57 | 3 | newResource = result; |
58 | 3 | postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback); |
59 | }, | |
60 | function(callback) { | |
61 | 3 | responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback); |
62 | }, | |
63 | function(sanitisedData, callback) { | |
64 | 3 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
65 | 3 | postProcess.handle(request, response, callback); |
66 | } | |
67 | ], function(err) { | |
68 | 13 | if (err) return helper.handleError(request, res, err); |
69 | 3 | router.sendResponse(res, response, 200); |
70 | }); | |
71 | }); | |
72 | }; | |
73 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var updateRelationRoute = module.exports = { }; |
4 | ||
5 | 1 | var async = require("async"); |
6 | 1 | var _ = { |
7 | assign: require("lodash.assign") | |
8 | }; | |
9 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
10 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
11 | 1 | var postProcess = require("/home/pmcnr/work/jsonapi-server/lib/routes/../postProcess.js"); |
12 | 1 | var responseHelper = require("/home/pmcnr/work/jsonapi-server/lib/routes/../responseHelper.js"); |
13 | ||
14 | ||
15 | 1 | updateRelationRoute.register = function() { |
16 | 20 | router.bindRoute({ |
17 | verb: "patch", | |
18 | path: ":type/:id/relationships/:relation" | |
19 | }, function(request, resourceConfig, res) { | |
20 | 4 | var newResource; |
21 | 4 | var theirResource; |
22 | 4 | var response; |
23 | ||
24 | 4 | async.waterfall([ |
25 | function(callback) { | |
26 | 4 | helper.verifyRequest(request, resourceConfig, res, "update", callback); |
27 | }, | |
28 | function(callback) { | |
29 | 3 | helper.verifyRequest(request, resourceConfig, res, "find", callback); |
30 | }, | |
31 | function(callback) { | |
32 | 3 | helper.checkForBody(request, callback); |
33 | }, | |
34 | function(callback) { | |
35 | 3 | var theirs = request.params.data; |
36 | 3 | theirResource = _.assign({ |
37 | id: request.params.id, | |
38 | type: request.params.type | |
39 | }); | |
40 | 3 | theirResource[request.params.relation] = theirs; |
41 | 3 | helper.validate(theirResource, resourceConfig.onCreate, callback); |
42 | }, | |
43 | function(callback) { | |
44 | 2 | resourceConfig.handlers.update(request, theirResource, callback); |
45 | }, | |
46 | function(result, callback) { | |
47 | 1 | resourceConfig.handlers.find(request, callback); |
48 | }, | |
49 | function(result, callback) { | |
50 | 1 | newResource = result; |
51 | 1 | postProcess.fetchForeignKeys(request, newResource, resourceConfig.attributes, callback); |
52 | }, | |
53 | function(callback) { | |
54 | 1 | responseHelper._enforceSchemaOnObject(newResource, resourceConfig.attributes, callback); |
55 | }, | |
56 | function(sanitisedData, callback) { | |
57 | 1 | sanitisedData = sanitisedData.relationships[request.params.relation].data; |
58 | 1 | response = responseHelper._generateResponse(request, resourceConfig, sanitisedData); |
59 | 1 | postProcess.handle(request, response, callback); |
60 | } | |
61 | ], function(err) { | |
62 | 5 | if (err) return helper.handleError(request, res, err); |
63 | 1 | router.sendResponse(res, response, 200); |
64 | }); | |
65 | }); | |
66 | }; | |
67 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var fourOhFour = module.exports = { }; |
4 | ||
5 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
6 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
7 | ||
8 | ||
9 | 1 | fourOhFour.register = function() { |
10 | 20 | router.bind404(function(request, res) { |
11 | 2 | return helper.handleError(request, res, { |
12 | status: "404", | |
13 | code: "EINVALID", | |
14 | title: "Invalid Route", | |
15 | detail: "This is not the API you are looking for?" | |
16 | }); | |
17 | }); | |
18 | }; | |
19 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var errorHandler = module.exports = { }; |
4 | ||
5 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/routes/../jsonApi.js"); |
6 | 1 | var helper = require("/home/pmcnr/work/jsonapi-server/lib/routes/./helper.js"); |
7 | 1 | var router = require("/home/pmcnr/work/jsonapi-server/lib/routes/../router.js"); |
8 | ||
9 | ||
10 | 1 | errorHandler.register = function() { |
11 | 20 | router.bindErrorHandler(function(request, res, error) { |
12 | ||
13 | 0 | if (jsonApi._errHandler) { |
14 | 0 | jsonApi._errHandler(request, error); |
15 | } | |
16 | ||
17 | 0 | return helper.handleError(request, res, { |
18 | status: "500", | |
19 | code: "EUNKNOWN", | |
20 | title: "An unknown error has occured. Sorry?", | |
21 | detail: "??" | |
22 | }); | |
23 | }); | |
24 | }; | |
25 |
Line | Hits | Source |
---|---|---|
1 | /* @flow weak */ | |
2 | 1 | "use strict"; |
3 | 1 | var swagger = module.exports = { }; |
4 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/swagger/../../"); |
5 | 1 | var swaggerPaths = require("/home/pmcnr/work/jsonapi-server/lib/swagger/./paths.js"); |
6 | 1 | var swaggerResources = require("/home/pmcnr/work/jsonapi-server/lib/swagger/./resources.js"); |
7 | ||
8 | 1 | swagger.generateDocumentation = function() { |
9 | 1 | var swaggerDoc = swagger._getSwaggerBase(); |
10 | 1 | swaggerDoc.paths = swaggerPaths.getPathDefinitions(jsonApi); |
11 | 1 | swaggerDoc.definitions = swaggerResources.getResourceDefinitions(jsonApi); |
12 | 1 | return swaggerDoc; |
13 | }; | |
14 | ||
15 | 1 | swagger._getSwaggerBase = function() { |
16 | 1 | var swaggerConfig = jsonApi._apiConfig.swagger || { }; |
17 | 1 | return { |
18 | swagger: "2.0", | |
19 | info: { | |
20 | title: swaggerConfig.title, | |
21 | version: swaggerConfig.version, | |
22 | description: swaggerConfig.description, | |
23 | contact: { | |
24 | name: (swaggerConfig.contact || { }).name, | |
25 | email: (swaggerConfig.contact || { }).email, | |
26 | url: (swaggerConfig.contact || { }).url | |
27 | }, | |
28 | license: { | |
29 | name: (swaggerConfig.license || { }).name, | |
30 | url: (swaggerConfig.license || { }).url | |
31 | } | |
32 | }, | |
33 | host: jsonApi._apiConfig.host, | |
34 | basePath: jsonApi._apiConfig.base.substring(0, jsonApi._apiConfig.base.length - 1), | |
35 | schemes: [ jsonApi._apiConfig.protocol ], | |
36 | consumes: [ | |
37 | "application/vnd.api+json" | |
38 | ], | |
39 | produces: [ | |
40 | "application/vnd.api+json" | |
41 | ], | |
42 | parameters: { | |
43 | sort: { | |
44 | name: "sort", | |
45 | in: "query", | |
46 | description: "Sort resources as per the JSON:API specification", | |
47 | required: false, | |
48 | type: "string" | |
49 | }, | |
50 | include: { | |
51 | name: "include", | |
52 | in: "query", | |
53 | description: "Fetch additional resources as per the JSON:API specification", | |
54 | required: false, | |
55 | type: "string" | |
56 | }, | |
57 | filter: { | |
58 | name: "filter", | |
59 | in: "query", | |
60 | description: "Filter resources as per the JSON:API specification", | |
61 | required: false, | |
62 | type: "string" | |
63 | }, | |
64 | fields: { | |
65 | name: "fields", | |
66 | in: "query", | |
67 | description: "Limit response payloads as per the JSON:API specification", | |
68 | required: false, | |
69 | type: "string" | |
70 | }, | |
71 | page: { | |
72 | name: "page", | |
73 | in: "query", | |
74 | description: "Pagination namespace", | |
75 | required: false, | |
76 | type: "string" | |
77 | } | |
78 | }, | |
79 | paths: { }, | |
80 | definitions: { } | |
81 | }; | |
82 | }; | |
83 |
Line | Hits | Source |
---|---|---|
1 | 1 | "use strict"; |
2 | 1 | var swaggerPaths = module.exports = { }; |
3 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/swagger/../../"); |
4 | 1 | var _ = { |
5 | uniq: require("lodash.uniq") | |
6 | }; | |
7 | ||
8 | ||
9 | 1 | swaggerPaths.getPathDefinitions = function() { |
10 | 1 | var paths = { }; |
11 | ||
12 | 1 | for (var resourceName in jsonApi._resources) { |
13 | 5 | var resourceConfig = jsonApi._resources[resourceName]; |
14 | 5 | swaggerPaths._addPathDefinition(paths, resourceConfig); |
15 | } | |
16 | ||
17 | 1 | return paths; |
18 | }; | |
19 | ||
20 | 1 | swaggerPaths._addPathDefinition = function(paths, resourceConfig) { |
21 | 5 | if (!paths || !resourceConfig) return undefined; |
22 | 5 | var resourceName = resourceConfig.resource; |
23 | ||
24 | 5 | swaggerPaths._addBasicPaths(paths, resourceName, resourceConfig); |
25 | ||
26 | 5 | Object.keys(resourceConfig.attributes).filter(function(relationName) { |
27 | 44 | var relation = resourceConfig.attributes[relationName]; |
28 | 44 | relation = relation._settings; |
29 | 81 | if (!relation || relation.__as) return false; |
30 | 7 | relation = relation.__many || relation.__one; |
31 | 7 | return (jsonApi._resources[relation] && jsonApi._resources[relation].handlers.find); |
32 | }).forEach(function(relationName) { | |
33 | 7 | var relation = resourceConfig.attributes[relationName]; |
34 | 7 | relation = relation._settings.__one || relation._settings.__many; |
35 | ||
36 | 7 | swaggerPaths._addDeepPaths(paths, resourceName, resourceConfig, relationName, relation); |
37 | }); | |
38 | }; | |
39 | ||
40 | 1 | swaggerPaths._addBasicPaths = function(paths, resourceName, resourceConfig) { |
41 | 5 | paths["/" + resourceName] = { |
42 | get: swaggerPaths._getPathOperationObject({ | |
43 | handler: "search", | |
44 | resourceName: resourceName, | |
45 | description: "Search for " + resourceName, | |
46 | parameters: resourceConfig.searchParams, | |
47 | hasPathId: false | |
48 | }), | |
49 | post: swaggerPaths._getPathOperationObject({ | |
50 | handler: "create", | |
51 | resourceName: resourceName, | |
52 | description: "Create a new instance of " + resourceName, | |
53 | parameters: resourceConfig.attributes, | |
54 | hasPathId: false | |
55 | }) | |
56 | }; | |
57 | ||
58 | 5 | paths["/" + resourceName + "/{id}"] = { |
59 | get: swaggerPaths._getPathOperationObject({ | |
60 | handler: "find", | |
61 | resourceName: resourceName, | |
62 | description: "Get a specific instance of " + resourceName, | |
63 | hasPathId: true | |
64 | }), | |
65 | delete: swaggerPaths._getPathOperationObject({ | |
66 | handler: "delete", | |
67 | resourceName: resourceName, | |
68 | description: "Delete an instance of " + resourceName, | |
69 | hasPathId: true | |
70 | }), | |
71 | patch: swaggerPaths._getPathOperationObject({ | |
72 | handler: "update", | |
73 | resourceName: resourceName, | |
74 | description: "Update an instance of " + resourceName, | |
75 | hasPathId: true | |
76 | }) | |
77 | }; | |
78 | }; | |
79 | ||
80 | 1 | swaggerPaths._addDeepPaths = function(paths, resourceName, resourceConfig, relationName, relation) { |
81 | 7 | paths["/" + resourceName + "/{id}/" + relationName] = { |
82 | get: swaggerPaths._getPathOperationObject({ | |
83 | handler: "find", | |
84 | resourceName: relation, | |
85 | hasPathId: true | |
86 | }) | |
87 | }; | |
88 | ||
89 | 7 | var relationType = resourceConfig.attributes[relationName]._settings.__many ? "many" : "one"; |
90 | 7 | paths["/" + resourceName + "/{id}/relationships/" + relationName] = { |
91 | get: swaggerPaths._getPathOperationObject({ | |
92 | handler: "find", | |
93 | resourceName: relation, | |
94 | relationType: relationType, | |
95 | extraTags: resourceName, | |
96 | hasPathId: true | |
97 | }), | |
98 | post: swaggerPaths._getPathOperationObject({ | |
99 | handler: "create", | |
100 | resourceName: relation, | |
101 | relationType: relationType, | |
102 | extraTags: resourceName, | |
103 | hasPathId: true | |
104 | }), | |
105 | patch: swaggerPaths._getPathOperationObject({ | |
106 | handler: "update", | |
107 | resourceName: relation, | |
108 | relationType: relationType, | |
109 | extraTags: resourceName, | |
110 | hasPathId: true | |
111 | }), | |
112 | delete: swaggerPaths._getPathOperationObject({ | |
113 | handler: "delete", | |
114 | resourceName: relation, | |
115 | relationType: relationType, | |
116 | extraTags: resourceName, | |
117 | hasPathId: true | |
118 | }) | |
119 | }; | |
120 | }; | |
121 | ||
122 | 1 | swaggerPaths._getPathOperationObject = function(options) { |
123 | 60 | var pathDefinition = { |
124 | tags: [ options.resourceName ], | |
125 | description: options.description, | |
126 | parameters: [ ], | |
127 | responses: { | |
128 | "200": { | |
129 | description: options.resourceName + " " + options.handler + " response", | |
130 | schema: { | |
131 | type: "object", | |
132 | required: [ "jsonapi", "meta", "links" ], | |
133 | properties: { | |
134 | jsonapi: { | |
135 | type: "object", | |
136 | required: [ "version" ], | |
137 | properties: { | |
138 | version: { | |
139 | type: "string" | |
140 | } | |
141 | } | |
142 | }, | |
143 | meta: { | |
144 | type: "object" | |
145 | }, | |
146 | links: { | |
147 | type: "object", | |
148 | required: [ "self" ], | |
149 | properties: { | |
150 | self: { | |
151 | type: "string" | |
152 | }, | |
153 | first: { | |
154 | type: "string" | |
155 | }, | |
156 | last: { | |
157 | type: "string" | |
158 | }, | |
159 | next: { | |
160 | type: "string" | |
161 | }, | |
162 | prev: { | |
163 | type: "string" | |
164 | } | |
165 | } | |
166 | } | |
167 | } | |
168 | } | |
169 | }, | |
170 | default: { | |
171 | description: "Unexpected error", | |
172 | schema: { | |
173 | "$ref": "#/definitions/error" | |
174 | } | |
175 | } | |
176 | } | |
177 | }; | |
178 | 60 | if (options.extraTags) { |
179 | 28 | pathDefinition.tags = pathDefinition.tags.concat(options.extraTags); |
180 | 28 | pathDefinition.tags = _.uniq(pathDefinition.tags); |
181 | } | |
182 | ||
183 | 60 | var responseShortcut = pathDefinition.responses["200"].schema.properties; |
184 | 60 | responseShortcut.data = { |
185 | "$ref": "#/definitions/" + options.resourceName | |
186 | }; | |
187 | ||
188 | 60 | if (options.handler === "search") { |
189 | 5 | responseShortcut.data = { |
190 | type: "array", | |
191 | items: responseShortcut.data | |
192 | }; | |
193 | } | |
194 | 60 | if (((options.handler === "search") || (options.handler === "find")) && !options.relation) { |
195 | 24 | pathDefinition.parameters = pathDefinition.parameters.concat(swaggerPaths._optionalJsonApiParameters()); |
196 | 24 | responseShortcut.included = { |
197 | type: "array", | |
198 | items: { | |
199 | type: "object" | |
200 | } | |
201 | }; | |
202 | } | |
203 | ||
204 | 60 | if ((options.handler === "create") || (options.handler === "update")) { |
205 | 24 | var body = swaggerPaths._getBaseResourceModel(options.resourceName); |
206 | 24 | if (options.relationType) { |
207 | 14 | body.schema.properties.data = swaggerPaths._getRelationModel(); |
208 | 14 | if ((options.handler === "update") && (options.relationType === "many")) { |
209 | 3 | body.schema.properties.data = { |
210 | type: "array", | |
211 | items: body.schema.properties.data | |
212 | }; | |
213 | } | |
214 | } | |
215 | 24 | pathDefinition.parameters = pathDefinition.parameters.concat(body); |
216 | } | |
217 | ||
218 | 60 | if (options.handler === "delete" && options.relationType) { |
219 | 7 | var body2 = swaggerPaths._getBaseResourceModel(options.resourceName); |
220 | 7 | body2.schema.properties.data = swaggerPaths._getRelationModel(); |
221 | 7 | pathDefinition.parameters = pathDefinition.parameters.concat(body2); |
222 | } | |
223 | ||
224 | ||
225 | 60 | if (options.handler === "delete") { |
226 | 12 | responseShortcut.data = undefined; |
227 | } | |
228 | ||
229 | 60 | if (options.handler === "create") { |
230 | 12 | pathDefinition.responses["201"] = pathDefinition.responses["200"]; |
231 | 12 | pathDefinition.responses["200"] = undefined; |
232 | } | |
233 | ||
234 | 60 | if (options.hasPathId) { |
235 | 50 | pathDefinition.parameters.push({ |
236 | name: "id", | |
237 | in: "path", | |
238 | description: "id of specific instance to lookup", | |
239 | required: true, | |
240 | type: "string" | |
241 | }); | |
242 | } | |
243 | ||
244 | 60 | if (options.parameters) { |
245 | 10 | var additionalParams = Object.keys(options.parameters).map(function(paramName) { |
246 | 75 | var joiScheme = options.parameters[paramName]; |
247 | 90 | if ((paramName === "id") || (paramName === "type")) return null; |
248 | ||
249 | 60 | return { |
250 | name: paramName, | |
251 | in: "query", | |
252 | description: joiScheme._description || undefined, | |
253 | required: ((joiScheme._flags || { }).presence === "required"), | |
254 | type: joiScheme._type | |
255 | }; | |
256 | }); | |
257 | 10 | pathDefinition.parameters.concat(additionalParams); |
258 | } | |
259 | ||
260 | 60 | if (options.relationType) { |
261 | 28 | responseShortcut.data = swaggerPaths._getRelationModel(); |
262 | 28 | if (options.relationType === "many") { |
263 | 12 | responseShortcut.data = { |
264 | type: "array", | |
265 | items: responseShortcut.data | |
266 | }; | |
267 | } | |
268 | } | |
269 | ||
270 | 60 | return pathDefinition; |
271 | }; | |
272 | ||
273 | 1 | swaggerPaths._optionalJsonApiParameters = function() { |
274 | 24 | return [ |
275 | { "$ref": "#/parameters/sort" }, | |
276 | { "$ref": "#/parameters/include" }, | |
277 | { "$ref": "#/parameters/filter" }, | |
278 | { "$ref": "#/parameters/fields" }, | |
279 | { "$ref": "#/parameters/page" } | |
280 | ]; | |
281 | }; | |
282 | ||
283 | 1 | swaggerPaths._getRelationModel = function() { |
284 | 49 | return { |
285 | type: "object", | |
286 | required: [ "type", "id" ], | |
287 | properties: { | |
288 | type: { | |
289 | type: "string" | |
290 | }, | |
291 | id: { | |
292 | type: "string" | |
293 | }, | |
294 | meta: { | |
295 | type: "object" | |
296 | } | |
297 | } | |
298 | }; | |
299 | }; | |
300 | ||
301 | 1 | swaggerPaths._getBaseResourceModel = function(resourceName) { |
302 | 31 | return { |
303 | in: "body", | |
304 | name: "body", | |
305 | description: "New or partial resource", | |
306 | required: true, | |
307 | schema: { | |
308 | type: "object", | |
309 | properties: { | |
310 | data: { | |
311 | "$ref": "#/definitions/" + resourceName | |
312 | } | |
313 | } | |
314 | } | |
315 | }; | |
316 | }; | |
317 |
Line | Hits | Source |
---|---|---|
1 | 1 | "use strict"; |
2 | 1 | var swaggerPaths = module.exports = { }; |
3 | 1 | var jsonApi = require("/home/pmcnr/work/jsonapi-server/lib/swagger/../../"); |
4 | ||
5 | ||
6 | 1 | swaggerPaths.getResourceDefinitions = function() { |
7 | 1 | var resourceDefinitions = { }; |
8 | ||
9 | 1 | for (var resource in jsonApi._resources) { |
10 | 5 | resourceDefinitions[resource] = swaggerPaths._getResourceDefinition(jsonApi._resources[resource]); |
11 | } | |
12 | 1 | resourceDefinitions.error = swaggerPaths._getErrorDefinition(); |
13 | ||
14 | 1 | return resourceDefinitions; |
15 | }; | |
16 | ||
17 | 1 | swaggerPaths._getResourceDefinition = function(resourceConfig) { |
18 | 5 | if (Object.keys(resourceConfig.handlers || { }).length === 0) return undefined; |
19 | ||
20 | 5 | var resourceDefinition = { |
21 | description: resourceConfig.description, | |
22 | type: "object", | |
23 | // required: [ "id", "type", "attributes", "relationships", "links" ], | |
24 | properties: { | |
25 | "id": { | |
26 | type: "string" | |
27 | }, | |
28 | "type": { | |
29 | type: "string" | |
30 | }, | |
31 | "attributes": { | |
32 | type: "object", | |
33 | properties: { } | |
34 | }, | |
35 | "relationships": { | |
36 | type: "object", | |
37 | properties: { } | |
38 | }, | |
39 | "links": { | |
40 | type: "object", | |
41 | properties: { | |
42 | self: { | |
43 | type: "string" | |
44 | } | |
45 | } | |
46 | }, | |
47 | "meta": { | |
48 | type: "object" | |
49 | } | |
50 | } | |
51 | }; | |
52 | 5 | var attributeShortcut = resourceDefinition.properties.attributes.properties; |
53 | 5 | var relationshipsShortcut = resourceDefinition.properties.relationships.properties; |
54 | ||
55 | ||
56 | 5 | var attributes = resourceConfig.attributes; |
57 | 5 | for (var attribute in attributes) { |
58 | 59 | if ((attribute === "id") || (attribute === "type") || (attribute === "meta")) continue; |
59 | ||
60 | 29 | var joiScheme = attributes[attribute]; |
61 | ||
62 | 29 | var swaggerScheme = { }; |
63 | 29 | if (joiScheme._description) { |
64 | 22 | swaggerScheme.description = joiScheme._description; |
65 | } | |
66 | ||
67 | 29 | if (!joiScheme._settings) { |
68 | 16 | swaggerScheme.type = joiScheme._type; |
69 | 16 | if (swaggerScheme.type === "date") { |
70 | 0 | swaggerScheme.type = "string"; |
71 | 0 | swaggerScheme.format = "date"; |
72 | } | |
73 | 16 | attributeShortcut[attribute] = swaggerScheme; |
74 | ||
75 | 16 | if ((joiScheme._flags || { }).presence === "required") { |
76 | 3 | resourceDefinition.properties.attributes.required = resourceDefinition.properties.attributes.required || [ ]; |
77 | 3 | resourceDefinition.properties.attributes.required.push(attribute); |
78 | } | |
79 | } else { | |
80 | 13 | if (joiScheme._settings.as) continue; |
81 | ||
82 | 13 | swaggerScheme = { |
83 | type: "object", | |
84 | properties: { | |
85 | meta: { | |
86 | type: "object" | |
87 | }, | |
88 | links: { | |
89 | type: "object", | |
90 | properties: { | |
91 | self: { | |
92 | type: "string" | |
93 | }, | |
94 | related: { | |
95 | type: "string" | |
96 | } | |
97 | } | |
98 | }, | |
99 | data: { | |
100 | type: "object", | |
101 | required: [ "type", "id" ], | |
102 | properties: { | |
103 | type: { | |
104 | type: "string" | |
105 | }, | |
106 | id: { | |
107 | type: "string" | |
108 | }, | |
109 | meta: { | |
110 | type: "object" | |
111 | } | |
112 | } | |
113 | } | |
114 | } | |
115 | }; | |
116 | ||
117 | 13 | if (joiScheme._settings.__many) { |
118 | 8 | swaggerScheme.properties.data = { |
119 | type: "array", | |
120 | items: swaggerScheme.properties.data | |
121 | }; | |
122 | } | |
123 | ||
124 | 13 | if ((joiScheme._flags || { }).presence === "required") { |
125 | 0 | if (joiScheme._settings.__many) { |
126 | 0 | swaggerScheme.required = true; |
127 | } else { | |
128 | 0 | swaggerScheme.required = [ "type", "id" ]; |
129 | } | |
130 | } | |
131 | 13 | relationshipsShortcut[attribute] = swaggerScheme; |
132 | } | |
133 | } | |
134 | ||
135 | 5 | return resourceDefinition; |
136 | }; | |
137 | ||
138 | 1 | swaggerPaths._getErrorDefinition = function() { |
139 | 1 | return { |
140 | type: "object", | |
141 | required: [ "jsonapi", "meta", "links", "errors" ], | |
142 | properties: { | |
143 | jsonapi: { | |
144 | type: "object", | |
145 | required: [ "version" ], | |
146 | properties: { | |
147 | version: { | |
148 | type: "string" | |
149 | } | |
150 | } | |
151 | }, | |
152 | meta: { | |
153 | type: "object" | |
154 | }, | |
155 | links: { | |
156 | type: "object", | |
157 | properties: { | |
158 | self: { | |
159 | type: "string" | |
160 | } | |
161 | } | |
162 | }, | |
163 | errors: { | |
164 | type: "array", | |
165 | items: { | |
166 | type: "object", | |
167 | required: [ | |
168 | "status", "code", "title", "detail" | |
169 | ], | |
170 | properties: { | |
171 | status: { | |
172 | type: "string" | |
173 | }, | |
174 | code: { | |
175 | type: "string" | |
176 | }, | |
177 | title: { | |
178 | type: "string" | |
179 | }, | |
180 | detail: { | |
181 | type: "object" | |
182 | } | |
183 | } | |
184 | } | |
185 | } | |
186 | } | |
187 | }; | |
188 | }; | |
189 |