diff --git a/Environmental Sensor Data Repository.iml b/Environmental Sensor Data Repository.iml index 8683894..fb37aa5 100644 --- a/Environmental Sensor Data Repository.iml +++ b/Environmental Sensor Data Repository.iml @@ -1,7 +1,6 @@ - - + diff --git a/Environmental Sensor Data Repository.ipr b/Environmental Sensor Data Repository.ipr index 6b55f56..a6aa564 100644 --- a/Environmental Sensor Data Repository.ipr +++ b/Environmental Sensor Data Repository.ipr @@ -40,42 +40,10 @@ - - - - - + + - - - + diff --git a/models/Devices.js b/models/Devices.js index 9721685..519e1a2 100644 --- a/models/Devices.js +++ b/models/Devices.js @@ -87,12 +87,16 @@ module.exports = function(databaseHelper) { // now validate jsonValidator.validate(device, JSON_SCHEMA, function(err1) { if (err1) { + console.log('* VALIDATION ERROR *'); + console.dir(err1); return callback(new ValidationError(err1)); } // now that we have the hashed secret, try to insert databaseHelper.execute("INSERT INTO Devices SET ?", device, function(err2, result) { if (err2) { + console.log('* INSERTION ERROR *'); + console.dir(err2); return callback(err2); } @@ -106,6 +110,18 @@ module.exports = function(databaseHelper) { }); }; + this.remove = function(deviceId, userId, callback) { + databaseHelper.execute("DELETE FROM Devices WHERE id = "+deviceId+" and userId = "+userId, null, function(err, result) { + if (err) { + return callback(err); + } else { + return callback(null, { + deviceId : deviceId + }); + } + }); + }; + /** * Tries to find the device with the given deviceId for the given authenticated user and returns it to * the given callback. If successful, the device is returned as the 2nd argument to the diff --git a/models/Products.js b/models/Products.js index dd90cbd..0a94607 100644 --- a/models/Products.js +++ b/models/Products.js @@ -122,6 +122,37 @@ module.exports = function(databaseHelper) { }); }; + this.update = function(productId, userId, body, callback) { + var setstring = ""; + console.log("-- update called for product id "+productId+", and userId = "+userId); + //console.dir(body); + for(var k in body){ + var v = body[k]; + setstring += +"="+v+","; + } + setstring = setstring.substring(0, setstring.length-1); + console.log("setstring is "+setstring); + databaseHelper.execute("UPDATE Products SET "+setstring+" WHERE id = "+productId+" and userId = "+userId, null, function(err, result) { + if (err) { + return callback(err); + } else { + return callback(null, result); + } + }); + }; + + this.remove = function(productId, userId, callback) { + databaseHelper.execute("DELETE FROM Products WHERE id = "+productId+" and userId = "+userId, null, function(err, result) { + if (err) { + return callback(err); + } else { + return callback(null, { + productId : productId + }); + } + }); + }; + /** * Tries to find the product with the given name and returns it to the given callback. If * successful, the product is returned as the 2nd argument to the callback function. If unsuccessful, diff --git a/routes/api/devices.js b/routes/api/devices.js index c7b5e50..60fa9a3 100644 --- a/routes/api/devices.js +++ b/routes/api/devices.js @@ -42,6 +42,32 @@ module.exports = function(DeviceModel, FeedModel) { }); }); + router.delete('/:id', + passport.authenticate('bearer', { session : false }), + function(req, res, next) { + var userId = req.user.id; + var deviceId = req.params.id; + log.debug("Received DELETE for device [" + deviceId + "]"); + DeviceModel.findByIdForUser(deviceId, userId, "id,userId", function(err, prod){ + //console.log("delete find err = "+err+", prod = "+prod); + //console.dir(prod); + if (err){ + console.dir(err); + console.log("Access Denied"); + res.jsendClientError("Access denied", err.data); + } + else if (prod.userId != userId) { + console.log("Access Denied, since creator userId is "+prod.userId+" and attempted deletor userId is "+userId); + res.jsendClientError("Access denied", null, httpStatus.FORBIDDEN); + } else { + console.log("Device removed."); + DeviceModel.remove(deviceId, userId, function(result) { + res.jsendSuccess(result); + }); + } + }); + }); + // create a feed for the specified device (specified by device ID) router.post('/:deviceId/feeds', passport.authenticate('bearer', { session : false }), diff --git a/routes/api/products.js b/routes/api/products.js index 7c2402d..9d69869 100644 --- a/routes/api/products.js +++ b/routes/api/products.js @@ -42,6 +42,21 @@ module.exports = function(ProductModel, DeviceModel) { }); }); + // update a product + router.put('/:id', + passport.authenticate('bearer', { session : false }), + function(req, res, next) { + var id = req.params.id; + var userId = req.user.id; + var newProduct = req.body; + log.debug("Received PUT from user ID [" + userId + "] to update product [" + (newProduct && newProduct.name ? newProduct.name : null) + "]"); + findProductByNameOrId(res, id, 'id', function(product) { + ProductModel.update(id, userId, newProduct, function(err, result){ + res.jsendSuccess(result, httpStatus.ACCEPTED); + }); + }); + }); + // find products router.get('/', function(req, res, next) { @@ -85,6 +100,25 @@ module.exports = function(ProductModel, DeviceModel) { }); }); + // delete a specific product + // TODO: Guard against products being deleted when there are devices that refer to them + router.delete('/:id', + passport.authenticate('bearer', { session : false }), + function(req, res, next) { + var userId = req.user.id; + var productId = req.params.id; + log.debug("Received DELETE for product [" + productId + "]"); + ProductModel.findById(productId, "id,creatorUserId", function(err, prod){ + if (prod.creatorUserId != userId) { + res.jsendClientError("Access denied", null, httpStatus.FORBIDDEN); + } else { + ProductModel.remove(productId, userId, function(result) { + res.jsendSuccess(result); + }); + } + }); + }); + // create a new device router.post('/:productNameOrId/devices', passport.authenticate('bearer', { session : false }), @@ -110,11 +144,10 @@ module.exports = function(ProductModel, DeviceModel) { return res.jsendServerError(message); } - log.debug("Created new device [" + result.serialNumber + "] with id [" + result.insertId + "] "); - return res.jsendSuccess({ id : result.insertId, name : result.name, + userId: req.user.id, serialNumber : result.serialNumber }, httpStatus.CREATED); // HTTP 201 Created }); diff --git a/test/index.js b/test/index.js index dedea29..dcb34ed 100644 --- a/test/index.js +++ b/test/index.js @@ -2104,6 +2104,8 @@ describe("ESDR", function() { }); + // -------------------------------------------------------------------------------------------------------------------- Products + describe("Products", function() { var productIds = {}; @@ -2469,6 +2471,71 @@ describe("ESDR", function() { }); }); + it("Should be able to update an existing product (with authentication)", function(done) { + testProduct1.description = "foobar"; + agent(url) + .put("/api/v1/products/"+productIds.testProduct1) + .set({ + Authorization : "Bearer " + accessTokens.testUser1.access_token + }) + .send(testProduct1) + .end(function(err, res) { + if (err) { + return done(err); + } + + res.should.have.property('status', httpStatus.ACCEPTED); + res.body.should.have.property('code', httpStatus.ACCEPTED); + res.body.should.have.property('status', 'success'); + res.body.should.have.property('data'); + res.body.data.should.have.property('id'); + res.body.data.should.have.property('description', testProduct1.description); + + // remember the product ID + productIds.testProduct1 = res.body.data.id; + + done(); + }); + }); + + it("Should be able to delete a product (with authentication)", function(done) { + agent(url) + .delete("/api/v1/products/"+productIds.testProduct1+"?fields=id") + .set({ + Authorization : "Bearer " + accessTokens.testUser1.access_token + }) + .end(function(err, res) { + if (err) { + return done(err); + } + + res.should.have.property('status', httpStatus.CREATED); + res.body.should.have.property('code', httpStatus.CREATED); + res.body.should.have.property('status', 'success'); + res.body.should.have.property('data'); + res.body.data.should.have.property('id'); + res.body.data.should.have.property('id', productIds.testProduct1); + + done(); + }); + }); + + it("Should fail to delete a product without authentication", function(done) { + agent(url) + .delete("/api/v1/products/"+productIds.testProduct1+"?fields=id") + .end(function(err, res) { + if (err) { + return done(err); + } + + res.should.have.property('status', httpStatus.UNAUTHORIZED); + + done(); + }); + }); + + // -------------------------------------------------------------------------------------------------------------------- Devices + describe("Devices", function() { var deviceIds = {}; @@ -2984,6 +3051,44 @@ describe("ESDR", function() { }); }); + it("Should be able to delete a device (with authentication)", function(done) { + agent(url) + .delete("/api/v1/devices/"+ deviceIds.testDevice1 + "?fields=id,serialNumber,userId") + .set({ + Authorization : "Bearer " + accessTokens.testUser1.access_token + }) + .end(function(err, res) { + if (err) { + return done(err); + } + + res.should.have.property('status', httpStatus.CREATED); + res.body.should.have.property('code', httpStatus.CREATED); + res.body.should.have.property('status', 'success'); + res.body.should.have.property('data'); + res.body.data.should.have.property('id'); + res.body.data.should.have.property('id', productIds.testProduct1); + + done(); + }); + }); + + it("Should fail to delete a device without authentication", function(done) { + agent(url) + .delete("/api/v1/devices/"+ deviceIds.testDevice1 + "?fields=id,serialNumber,userId") + .end(function(err, res) { + if (err) { + return done(err); + } + + res.should.have.property('status', httpStatus.UNAUTHORIZED); + + done(); + }); + }); + + // ---------------------------------------------------------------------------------------------------------- Feeds + describe("Feeds", function() { var feeds = {};