diff --git a/.gitignore b/.gitignore index 79f4c92f..50904f29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules -coverage.html +coverage complexity npm-debug.log jsonapi-server.cpuprofile diff --git a/example/resources/photos.js b/example/resources/photos.js index f484a340..ed1e7907 100644 --- a/example/resources/photos.js +++ b/example/resources/photos.js @@ -64,6 +64,18 @@ jsonApi.define({ width: 350, tags: ['black', 'green'], photographer: { type: 'people', id: 'ad3aa89e-9c5b-4ac9-a652-6670f9f27587' } + }, + { + id: 'ed45eba1-15fe-41c7-93da-1df3dfa5289f', + type: 'photos', + title: 'Sunset Horizon', + url: 'http://www.example.com/sunset', + height: 450, + width: 1050, + raw: true, + tags: ['orange', 'sky', 'sun'], + photographer: { type: 'people', id: 'cc5cca2e-0dd8-4b95-8cfc-a11230e73116' } } + ] }) diff --git a/lib/filter.js b/lib/filter.js index 41214e7b..9bbe6a33 100644 --- a/lib/filter.js +++ b/lib/filter.js @@ -67,8 +67,6 @@ filter._parseScalarFilterElement = (attributeConfig, scalarElement) => { filter._parseFilterElementHelper = (attributeConfig, filterElement) => { if (!filterElement) return { error: 'invalid or empty filter element' } - if (typeof filterElement === 'string') filterElement = filterElement.split(FILTER_SEPERATOR) - const parsedElements = [].concat(filterElement).map(scalarElement => filter._parseScalarFilterElement(attributeConfig, scalarElement)) if (parsedElements.length === 1) return parsedElements[0] @@ -117,6 +115,8 @@ filter.parseAndValidate = request => { for (const key in request.params.filter) { filterElement = request.params.filter[key] + if (typeof filterElement === 'string') request.params.filter[key] = filterElement = filterElement.split(FILTER_SEPERATOR) + if (!Array.isArray(filterElement) && filterElement instanceof Object) continue // skip deep filters error = filter._resourceDoesNotHaveProperty(resourceConfig, key) diff --git a/lib/graphQl/index.js b/lib/graphQl/index.js index efa8503f..a408d4af 100644 --- a/lib/graphQl/index.js +++ b/lib/graphQl/index.js @@ -13,10 +13,12 @@ const writeTypes = require('./writeTypes.js') jsonApiGraphQL.with = app => { const config = jsonApi._apiConfig - app.use(new RegExp(`${config.base}$`), graphqlHTTP({ - schema: jsonApiGraphQL.generate(jsonApi._resources), - graphiql: !!config.graphiql - })) + if (config.graphiql !== false) { + app.use(new RegExp(`${config.base}$`), graphqlHTTP({ + schema: jsonApiGraphQL.generate(jsonApi._resources), + graphiql: !!config.graphiql + })) + } } jsonApiGraphQL.generate = allResourceConfig => { diff --git a/lib/postProcessing/include.js b/lib/postProcessing/include.js index 33101ddb..a6985dbc 100644 --- a/lib/postProcessing/include.js +++ b/lib/postProcessing/include.js @@ -12,7 +12,7 @@ const debug = require('../debugging.js') includePP.action = (request, response, callback) => { let includes = request.params.include - const filters = request.params.filter || { } + const filters = request.params.filter if (!includes) return callback() includes = (`${includes}`).split(',') @@ -47,6 +47,7 @@ includePP._arrayToTree = (request, includes, filters, callback) => { const parts = text.split('.') const first = parts.shift() const rest = parts.join('.') + if (!filter) filter = {} let resourceAttribute = node._resourceConfig.map(resourceConfig => { return resourceConfig.attributes[first] @@ -70,6 +71,7 @@ includePP._arrayToTree = (request, includes, filters, callback) => { } filter = filter[first] || { } + if (filter instanceof Array) { filter = filter.filter(i => i instanceof Object).pop() } @@ -159,7 +161,7 @@ includePP._fillIncludeTree = (includeTree, request, callback) => { ids += `&${includeTree[parts[1]]._filter.join('&')}` } resourcesToFetch.push({ - url: `${jsonApi._apiConfig.pathPrefix + parts[0]}/?${ids}`, + url: `${jsonApi._apiConfig.base + parts[0]}/?${ids}`, as: relation }) }) @@ -173,7 +175,7 @@ includePP._fillIncludeTree = (includeTree, request, callback) => { ids += `&${includeTree[parts[2]]._filter.join('&')}` } resourcesToFetch.push({ - url: `${jsonApi._apiConfig.pathPrefix + parts[1]}/?${ids}`, + url: `${jsonApi._apiConfig.base + parts[1]}/?${ids}`, as: relation }) }) diff --git a/lib/rerouter.js b/lib/rerouter.js index d073dc2e..4b9c7faa 100644 --- a/lib/rerouter.js +++ b/lib/rerouter.js @@ -18,6 +18,7 @@ rerouter.route = (newRequest, callback) => { const req = { url: newRequest.uri, + originalUrl: newRequest.originalRequest.originalUrl, headers: newRequest.originalRequest.headers, cookies: newRequest.originalRequest.cookies, params: rerouter._mergeParams(url.parse(newRequest.uri.split('?')[1] || { }), newRequest.params) diff --git a/lib/router.js b/lib/router.js index bb287bf9..ad67c51c 100644 --- a/lib/router.js +++ b/lib/router.js @@ -184,6 +184,7 @@ router._getParams = req => { headers: req.headers, safeHeaders: _.omit(req.headers, headersToRemove), cookies: req.cookies, + originalUrl: req.originalUrl, route: { verb: req.method, host: req.headers.host, diff --git a/lib/swagger/index.js b/lib/swagger/index.js index 20a2bd30..32dfa1ee 100644 --- a/lib/swagger/index.js +++ b/lib/swagger/index.js @@ -17,7 +17,7 @@ swagger._getSwaggerBase = () => { let basePath, host, protocol if (jsonApi._apiConfig.urlPrefixAlias) { const urlObj = url.parse(jsonApi._apiConfig.urlPrefixAlias) - basePath = urlObj.pathname.replace(/\/$/, '') + basePath = urlObj.pathname.replace(/(?!^\/)\/$/, '') host = urlObj.host protocol = urlObj.protocol.replace(/:$/, '') } else { diff --git a/test/_preResourceValidationCheck.js b/test/_preResourceValidationCheck.js index d5fedf6b..21f8b28c 100644 --- a/test/_preResourceValidationCheck.js +++ b/test/_preResourceValidationCheck.js @@ -6,7 +6,7 @@ describe('Testing jsonapi-server', () => { [ { name: 'articles', count: 4 }, { name: 'comments', count: 3 }, { name: 'people', count: 4 }, - { name: 'photos', count: 3 }, + { name: 'photos', count: 4 }, { name: 'tags', count: 5 } ].forEach(resource => { describe(`Searching for ${resource.name}`, () => { diff --git a/test/get-resource.js b/test/get-resource.js index 79cdcc2c..a588d31b 100644 --- a/test/get-resource.js +++ b/test/get-resource.js @@ -201,7 +201,7 @@ describe('Testing jsonapi-server', () => { assert.equal(res.statusCode, '200', 'Expecting 200 OK') const photoTypes = json.data.map(i => i.attributes.raw) - assert.deepEqual(photoTypes, [ true ], 'expected matching resources') + assert.deepEqual(photoTypes, [ true, true ], 'expected matching resources') done() }) @@ -530,13 +530,13 @@ describe('Testing jsonapi-server', () => { json = helpers.validateJson(json) assert.equal(res.statusCode, '200', 'Expecting 200 OK') - assert.equal(json.included.length, 7, 'Should be 7 included resources') + assert.equal(json.included.length, 8, 'Should be 8 included resources') const people = json.included.filter(resource => resource.type === 'people') assert.equal(people.length, 4, 'Should be 4 included people resources') const photos = json.included.filter(resource => resource.type === 'photos') - assert.equal(photos.length, 3, 'Should be 3 included photos resources') + assert.equal(photos.length, 4, 'Should be 4 included photos resources') done() }) @@ -552,13 +552,13 @@ describe('Testing jsonapi-server', () => { json = helpers.validateJson(json) assert.equal(res.statusCode, '200', 'Expecting 200 OK') - assert.equal(json.included.length, 7, 'Should be 7 included resources') + assert.equal(json.included.length, 8, 'Should be 8 included resources') const people = json.included.filter(resource => resource.type === 'people') assert.equal(people.length, 4, 'Should be 4 included people resources') const photos = json.included.filter(resource => resource.type === 'photos') - assert.equal(photos.length, 3, 'Should be 3 included photos resources') + assert.equal(photos.length, 4, 'Should be 4 included photos resources') done() }) @@ -585,6 +585,50 @@ describe('Testing jsonapi-server', () => { done() }) }) + + it('include author.photos with multiple filters', done => { + const url = 'http://localhost:16006/rest/articles?include=author.photos&filter[author]=ad3aa89e-9c5b-4ac9-a652-6670f9f27587&filter[author]=cc5cca2e-0dd8-4b95-8cfc-a11230e73116' + helpers.request({ + method: 'GET', + url + }, (err, res, json) => { + assert.equal(err, null) + json = helpers.validateJson(json) + + assert.equal(res.statusCode, '200', 'Expecting 200 OK') + assert.equal(json.included.length, 5, 'Should be 2 included resources') + + const people = json.included.filter(resource => resource.type === 'people') + assert.equal(people.length, 2, 'Should be 2 included people resource') + + const photos = json.included.filter(resource => resource.type === 'photos') + assert.equal(photos.length, 3, 'Should be 2 included photos resource') + + done() + }) + }) + + it('include author.photos with multiple filters comma delineated', done => { + const url = 'http://localhost:16006/rest/articles?include=author.photos&filter[author][firstname]=Mark,Oli' + helpers.request({ + method: 'GET', + url + }, (err, res, json) => { + assert.equal(err, null) + json = helpers.validateJson(json) + + assert.equal(res.statusCode, '200', 'Expecting 200 OK') + assert.equal(json.included.length, 4, 'Should be 2 included resources') + + const people = json.included.filter(resource => resource.type === 'people') + assert.equal(people.length, 2, 'Should be 2 included people resource') + + const photos = json.included.filter(resource => resource.type === 'photos') + assert.equal(photos.length, 2, 'Should be 2 included photos resource') + + done() + }) + }) }) describe('by foreign key', () => { diff --git a/test/zzPostResourceValidationCheck.js b/test/zzPostResourceValidationCheck.js index be772276..1e9b1652 100644 --- a/test/zzPostResourceValidationCheck.js +++ b/test/zzPostResourceValidationCheck.js @@ -6,7 +6,7 @@ describe('Testing jsonapi-server', () => { [ { name: 'articles', count: 4 }, { name: 'comments', count: 2 }, { name: 'people', count: 4 }, - { name: 'photos', count: 4 }, + { name: 'photos', count: 5 }, { name: 'tags', count: 5 } ].forEach(resource => { describe(`Searching for ${resource.name}`, () => {