diff --git a/caracal.js b/caracal.js index 046fb19..c71e293 100644 --- a/caracal.js +++ b/caracal.js @@ -55,77 +55,96 @@ app.use('/loader/', loaderHandler); // data, mongo app.use('/data', auth.loginHandler(auth.PUBKEY)); // slide -app.use('/data/Slide/find', dataHandlers.Slide.find); -app.use('/data/Slide/find', auth.filterHandler('data', 'userFilter', 'filter')); -app.use('/data/Slide/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Slide/post', dataHandlers.Slide.add); -app.use('/data/Slide/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Slide/delete', dataHandlers.Slide.delete); -app.use('/data/Slide/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Slide/update', dataHandlers.Slide.update); +app.get('/data/Slide/find', dataHandlers.Slide.find); +app.get('/data/Slide/find', auth.filterHandler('data', 'userFilter', 'filter')); +app.post('/data/Slide/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Slide/post', dataHandlers.Slide.add); +app.delete('/data/Slide/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Slide/delete', dataHandlers.Slide.find); +app.delete('/data/Slide/delete', auth.editHandler('data', 'userFilter', 'filter')); +app.delete('/data/Slide/delete', dataHandlers.Slide.delete); +app.post('/data/Slide/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Slide/update', dataHandlers.Slide.find); +app.post('/data/Slide/update', auth.editHandler('data', 'userFilter', 'filter')); +app.post('/data/Slide/update', dataHandlers.Slide.update); // mark -app.use('/data/Mark/find', dataHandlers.Mark.find); -app.use('/data/Mark/spatial', dataHandlers.Mark.spatial); -app.use('/data/Mark/multi', dataHandlers.Mark.multi); -app.use('/data/Mark/types', dataHandlers.Mark.types); -app.use('/data/Mark/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Mark/post', dataHandlers.Mark.add); -app.use('/data/Mark/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Mark/delete', dataHandlers.Mark.delete); -app.use('/data/Mark/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Mark/update', dataHandlers.Mark.update); +app.get('/data/Mark/find', dataHandlers.Mark.find); +app.get('/data/Mark/spatial', dataHandlers.Mark.spatial); +app.get('/data/Mark/multi', dataHandlers.Mark.multi); +app.get('/data/Mark/types', dataHandlers.Mark.types); +app.post('/data/Mark/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Mark/post', dataHandlers.Mark.add); +app.delete('/data/Mark/delete', dataHandlers.Mark.find); +app.delete('/data/Mark/delete', auth.editHandler('data')); +app.delete('/data/Mark/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Mark/delete', dataHandlers.Mark.delete); +app.post('/data/Mark/update', dataHandlers.Mark.find); +app.post('/data/Mark/update', auth.editHandler('data')); +app.post('/data/Mark/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Mark/update', dataHandlers.Mark.update); // template -app.use('/data/Template/find', dataHandlers.Template.find); -app.use('/data/Template/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Template/post', dataHandlers.Template.add); -app.use('/data/Template/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Template/delete', dataHandlers.Template.delete); -app.use('/data/Template/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Template/update', dataHandlers.Template.update); +app.get('/data/Template/find', dataHandlers.Template.find); +app.post('/data/Template/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Template/post', dataHandlers.Template.add); +app.delete('/data/Template/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Template/delete', dataHandlers.Template.delete); +app.post('/data/Template/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Template/update', dataHandlers.Template.update); // heatmap -app.use('/data/Heatmap/find', dataHandlers.Heatmap.find); -app.use('/data/Heatmap/types', dataHandlers.Heatmap.types); -app.use('/data/Heatmap/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Heatmap/post', dataHandlers.Heatmap.add); -app.use('/data/Heatmap/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Heatmap/delete', dataHandlers.Heatmap.delete); -app.use('/data/Heatmap/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Heatmap/update', dataHandlers.Heatmap.update); +app.get('/data/Heatmap/find', dataHandlers.Heatmap.find); +app.get('/data/Heatmap/types', dataHandlers.Heatmap.types); +app.post('/data/Heatmap/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Heatmap/post', dataHandlers.Heatmap.add); +app.delete('/data/Heatmap/delete', dataHandlers.Heatmap.find); +app.delete('/data/Heatmap/delete', auth.editHandler('data')); +app.delete('/data/Heatmap/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Heatmap/delete', dataHandlers.Heatmap.delete); +app.post('/data/Heatmap/update', dataHandlers.Heatmap.find); +app.post('/data/Heatmap/update', auth.editHandler('data')); +app.post('/data/Heatmap/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Heatmap/update', dataHandlers.Heatmap.update); // heatmapEdit -app.use('/data/HeatmapEdit/find', dataHandlers.HeatmapEdit.find); -app.use('/data/HeatmapEdit/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/HeatmapEdit/post', dataHandlers.HeatmapEdit.add); -app.use('/data/HeatmapEdit/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.delete); -app.use('/data/HeatmapEdit/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.update); +app.get('/data/HeatmapEdit/find', dataHandlers.HeatmapEdit.find); +app.post('/data/HeatmapEdit/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/HeatmapEdit/post', dataHandlers.HeatmapEdit.add); +app.delete('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.find); +app.delete('/data/HeatmapEdit/delete', auth.editHandler('data')); +app.delete('/data/HeatmapEdit/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/HeatmapEdit/delete', dataHandlers.HeatmapEdit.delete); +app.post('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.find); +app.post('/data/HeatmapEdit/update', auth.editHandler('data')); +app.post('/data/HeatmapEdit/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/HeatmapEdit/update', dataHandlers.HeatmapEdit.update); // log -app.use('/data/Log/find', dataHandlers.Log.find); +app.get('/data/Log/find', dataHandlers.Log.find); // anyone can add a log -app.use('/data/Log/post', dataHandlers.Log.add); -app.use('/data/Log/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Log/delete', dataHandlers.Log.delete); -app.use('/data/Log/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Log/update', dataHandlers.Log.update); +app.post('/data/Log/post', dataHandlers.Log.add); +app.delete('/data/Log/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Log/delete', dataHandlers.Log.delete); +app.post('/data/Log/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Log/update', dataHandlers.Log.update); // config -app.use('/data/Configuration/find', dataHandlers.Config.find); -app.use('/data/Configuration/post', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Configuration/post', dataHandlers.Config.add); -app.use('/data/Configuration/delete', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Configuration/delete', dataHandlers.Config.delete); -app.use('/data/Configuration/update', permissionHandler(['Admin', 'Editor'])); -app.use('/data/Configuration/update', dataHandlers.Config.update); +app.get('/data/Configuration/find', dataHandlers.Config.find); +app.post('/data/Configuration/post', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Configuration/post', dataHandlers.Config.add); +app.delete('/data/Configuration/delete', permissionHandler(['Admin', 'Editor'])); +app.delete('/data/Configuration/delete', dataHandlers.Config.delete); +app.post('/data/Configuration/update', permissionHandler(['Admin', 'Editor'])); +app.post('/data/Configuration/update', dataHandlers.Config.update); // user -app.use('/data/User/find', dataHandlers.User.find); -app.use('/data/User/post', permissionHandler(['Admin'])); -app.use('/data/User/post', dataHandlers.User.add); -app.use('/data/User/delete', permissionHandler(['Admin'])); -app.use('/data/user/delete', dataHandlers.User.delete); -app.use('/data/User/update', permissionHandler(['Admin'])); -app.use('/data/User/update', dataHandlers.User.update); +app.get('/data/User/find', dataHandlers.User.find); +app.post('/data/User/post', permissionHandler(['Admin'])); +app.post('/data/User/post', dataHandlers.User.add); +app.delete('/data/User/delete', permissionHandler(['Admin'])); +app.delete('/data/user/delete', dataHandlers.User.delete); +app.post('/data/User/update', permissionHandler(['Admin'])); +app.post('/data/User/update', dataHandlers.User.update); // render mongo returns/data app.use('/data', function(req, res, next) { + if (!req.data) { + res.status(404).json({}); + } res.json(req.data); }); diff --git a/handlers/authHandlers.js b/handlers/authHandlers.js index 57423ea..39dc5e7 100644 --- a/handlers/authHandlers.js +++ b/handlers/authHandlers.js @@ -3,6 +3,7 @@ var jwt = require('jsonwebtoken'); const jwksClient = require('jwks-rsa'); var atob = require('atob'); var fs = require('fs'); +var filterFunction = require('./filterFunction.js'); var JWK_URL = process.env.JWK_URL; var DISABLE_SEC = process.env.DISABLE_SEC || false; @@ -193,29 +194,41 @@ function loginHandler(checkKey) { // use filter handler AFTER data handler function filterHandler(dataField, filterField, attrField) { return function(req, res, next) { - // do nothing if sec disabled, or if filter contains "**" - // all docs with no set attrField are passed too - var filter = req[filterField]; - // make filter an array - if (!Array.isArray(filter)) { - filter = [filter]; + req[dataField] = filterFunction(req[filterField], req[dataField], attrField, '**'); + next(); + }; +} + +// use edit handler AFTER a find route to populate data, but BEFORE the edit itself +function editHandler(dataField, filterField, attrField) { + return function(req, res, next) { + if (filterField && attrField) { + req[dataField] = filterFunction(req[filterField], req[dataField], attrField, '**'); } - if (filter.indexOf('**') == -1) { - // filter data in dataField if attrField in filterField - var data = req[dataField]; - // is data an array? - if (Array.isArray(data)) { - // remove ones where does not match - req[dataField] = data.filter((x) => (!x[attrField] || filter.indexOf(x[attrField]) >= 0) ); - } else { - if (!data[attrField] || filter.indexOf(data[attrField]) >= 0) { - req[dataField] = data; - } else { - req[dataField] = {}; + if (!Array.isArray(req[dataField])) { + req[dataField] = [req[dataField]]; + } + // edit routes should operate on one object + if (req[dataField].length == 1) { + // DESTROY query + for (let n in req.query) { + if (req.query.hasOwnProperty(n)) { + delete req.query[n]; } } + req.query = {_id: req[dataField][0]._id['$oid']}; + next(); + } else if (req[dataField].length == 0) { + let errorMessage = {}; + errorMessage.error = 'Nothing applicable to change.'; + errorMessage.statusCode = 400; + next(errorMessage); + } else { + let errorMessage = {}; + errorMessage.error = 'At most one document may be changed at once.'; + errorMessage.statusCode = 400; + next(errorMessage); } - next(); }; } @@ -224,6 +237,7 @@ auth.jwkTokenTrade = jwkTokenTrade; auth.tokenTrade = tokenTrade; auth.filterHandler = filterHandler; auth.loginHandler = loginHandler; +auth.editHandler = editHandler; auth.CLIENT = CLIENT; auth.PRIKEY = PRIKEY; auth.PUBKEY = PUBKEY; diff --git a/handlers/dataHandlers.js b/handlers/dataHandlers.js index 3921233..38e06be 100644 --- a/handlers/dataHandlers.js +++ b/handlers/dataHandlers.js @@ -103,6 +103,7 @@ function mongoDelete(collection, query) { if (err) { rej(err); } + delete result.connection; res(result); db.close(); }); @@ -130,6 +131,7 @@ function mongoUpdate(collection, query, newVals) { if (err) { rej(err); } + delete result.connection; res(result); db.close(); }); @@ -142,7 +144,6 @@ function mongoUpdate(collection, query, newVals) { }); } - var Slide = {}; Slide.find = function(req, res, next) { // slide, specimen, study, location @@ -176,7 +177,7 @@ Slide.update = function(req, res, next) { var newVals = { $set: JSON.parse(req.body), }; - mongoUpdate('slide', query).then((x) => { + mongoUpdate('slide', query, newVals).then((x) => { req.data = x; next(); }).catch((e) => next(e)); diff --git a/handlers/filterFunction.js b/handlers/filterFunction.js new file mode 100644 index 0000000..c33da96 --- /dev/null +++ b/handlers/filterFunction.js @@ -0,0 +1,23 @@ +function filterFunction(filter, data, attr, wildcard) { + // make filter an array + if (!Array.isArray(filter)) { + filter = [filter]; + } + if (filter.indexOf(wildcard) == -1) { + // is data an array? + if (Array.isArray(data)) { + // remove ones where does not match + data = data.filter((x) => (!x[attr] || filter.indexOf(x[attr]) >= 0) ); + } else { + if (!data[attr] || filter.indexOf(data[attr]) >= 0) { + data = data; + } else { + data = {}; + } + } + } + return data; +} + + +module.exports = filterFunction; diff --git a/test/functional/slide_lifecycle.js b/test/functional/slide_lifecycle.js index 90c936b..6403d46 100644 --- a/test/functional/slide_lifecycle.js +++ b/test/functional/slide_lifecycle.js @@ -32,11 +32,9 @@ describe('Slide Lifecycle Step 1', function() { describe('Slide Lifecycle Step 2', function() { it('Finds the Slide', function(done) { this.timeout(5000); - var slideData = {'name': 'TEST', 'specimen': '', 'study': '', 'location': '/images/sample.svs', 'mpp': 0.499}; chai.request(server) - .post(findurl) + .get(findurl + '?name=TEST') .set('Content-Type', 'application/json; charset=utf-8') - .send(slideData) .end(function(err, res) { (res).should.have.status(200); (res.body).should.be.an('array'); @@ -48,11 +46,9 @@ describe('Slide Lifecycle Step 2', function() { describe('Slide Lifecycle Step 3', function() { it('Deletes a Slide', function(done) { this.timeout(5000); - var slideData = {'name': 'TEST', 'specimen': '', 'study': '', 'location': '/images/sample.svs', 'mpp': 0.499}; chai.request(server) - .post(deleteurl) + .delete(deleteurl + '?name=TEST') .set('Content-Type', 'application/json; charset=utf-8') - .send(slideData) .end(function(err, res) { (res).should.have.status(200); (res.body).should.be.a('object');